diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..4f8dd2e --- /dev/null +++ b/.drone.yml @@ -0,0 +1,74 @@ +kind: pipeline +name: default + +steps: + - name: Build Bullseye + image: debian:bullseye + volumes: + - name: finished_files + path: /deb_files + commands: + - apt update + - apt -y upgrade + - apt -y install --no-install-recommends build-essential equivs devscripts git rename + - git clean -f -d -x + - mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + - dpkg-buildpackage -b -uc + - rename 's/\.deb/_bullseye\.deb/' ../*.deb + - mkdir -p /deb_files/bullseye/ + - cp ../tinc*.deb /deb_files/bullseye/ + - find /deb_files/ + + - name: Build Buster + image: debian:buster + volumes: + - name: finished_files + path: /deb_files + commands: + - apt update + - apt -y upgrade + - apt -y install --no-install-recommends build-essential equivs devscripts git rename + - git clean -f -d -x + - mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + - dpkg-buildpackage -b -uc + - rename 's/\.deb/_buster\.deb/' ../*.deb + - mkdir -p /deb_files/buster/ + - cp ../tinc*.deb /deb_files/buster/ + - find /deb_files/ + + - name: Build Ubuntu Focal + image: ubuntu:focal + volumes: + - name: finished_files + path: /deb_files + commands: + - apt update + - apt -y upgrade + - DEBIAN_FRONTEND=noninteractive apt -y install --no-install-recommends build-essential equivs devscripts git rename + - git clean -f -d -x + - mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control + - dpkg-buildpackage -b -uc + - rename 's/\.deb/_focal\.deb/' ../*.deb + - mkdir -p /deb_files/focal/ + - cp ../tinc*.deb /deb_files/focal/ + - find /deb_files/ + + - name: gitea_release + image: plugins/gitea-release + volumes: + - name: finished_files + path: /deb_files + settings: + api_key: + from_secret: GITEA_KEY + base_url: https://git.neulandlabor.de/ + files: + - /deb_files/buster/* + - /deb_files/bullseye/* + - /deb_files/focal/* + when: + event: tag + +volumes: + - name: finished_files + temp: {} diff --git a/AUTHORS b/AUTHORS index af11393..07939e8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,20 +1,25 @@ Main tinc authors: + - Guus Sliepen - Ivo Timmermans (inactive) -Significant contributions from: -- Michael Tokarev +Significant code contributions from: + +- Brandon Black +- Etienne Dechamps - Florian Forster - Grzegorz Dymarek -- Max Rijevski -- Scott Lamb - Julien Muchembled -- Timothy Redaelli -- Brandon Black - Loïc Grenié +- Max Rijevski +- Michael Tokarev +- Scott Lamb +- Sven-Haegar Koch +- Timothy Redaelli These files are from other sources: - * lib/pidfile.h and lib/pidfile.c are by Martin Schulze, taken from + +* lib/pidfile.h and lib/pidfile.c are by Martin Schulze, taken from the syslog 1.3 sources. * src/bsd/tunemu.c and tunemu.h are by Friedrich Schöller @@ -23,5 +28,4 @@ These files are from other sources: Also some of the macro files in the directory m4, and their accompanying files in lib, were taken from GNU fileutils. -Please see the file THANKS for more information on contributions from -users. +Please see the file THANKS for a list of all contributors to tinc. diff --git a/COPYING b/COPYING index 1384f46..5436452 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 1998-2019 Ivo Timmermans, Guus Sliepen and others. +Copyright (C) 1998-2021 Ivo Timmermans, Guus Sliepen and others. See the AUTHORS file for a complete list. This program is free software; you can redistribute it and/or modify it under diff --git a/COPYING.README b/COPYING.README index 2eb9c1f..166102b 100644 --- a/COPYING.README +++ b/COPYING.README @@ -1,19 +1,17 @@ The following applies to tinc: -This program is released under the GPL with the additional exemption that -compiling, linking, and/or using OpenSSL is allowed. You may provide binary -packages linked to the OpenSSL libraries, provided that all other requirements -of the GPL are met. +> This program is released under the GPL with the additional exemption that +> compiling, linking, and/or using OpenSSL is allowed. You may provide binary +> packages linked to the OpenSSL libraries, provided that all other requirements +> of the GPL are met. The following applies to the LZO library: - Hereby I grant a special exception to the tinc VPN project - (http://tinc.nl.linux.org/) to link the LZO library with the OpenSSL library - (http://www.openssl.org). - - Markus F.X.J. Oberhumer +> Hereby I grant a special exception to the tinc VPN project +> (https://www.tinc-vpn.org/) to link the LZO library with the OpenSSL library +> (https://openssl.org). +> +> Markus F.X.J. Oberhumer When tinc is compiled with the --enable-tunemu option, the resulting binary falls under the GPL version 3 or later. - - diff --git a/ChangeLog b/ChangeLog index 041bf3f..32cc674 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,217 +1,578 @@ -Version 1.0.36 August 26 2019 +Version 1.1pre18 June 27 2021 ------------------------------------------------------------------------ -Guus Sliepen (8): - Remove the call to RAND_load_file(). +Guus Sliepen (39): + Fix the compiler attribute test to work with Clang. + Install the bash completion file when running make install. + Use the onlink flag when adding routes on Linux. + Check all Address statements when making outgoing connections. + Reformat the code using astyle. + Fix building with --disable-legacy-protocol. + Make more variables safe for use in invitations. + Allow "tinc --force join" to accept all variables sent in an invitaiton. + Skip the legacy protocol test if that protocol is disabled. + Fix warnings when compiling for Windows. + Fix compiling test binaries on Windows. + Drop support for Cygwin. + Make sure the stop command works on Windows if tincd is running in the foreground. + Prevent sptps_test from sending overly large UDP packets. + Attempt to make the test suite work with Windows executables. + Handle DOS line endings in invitation files. + Double-quote node names in dump graph output. + Fix the scripts test. + Prevent large amounts of UDP probes being sent consecutively. + Avoid void pointer arithmetic. + Disable AutoConnect in the ns-ping test. + Don't keep an address cache in an outgoing_t. + Try harder to connect to unreachable nodes. + Include stddef.h if available. + Fix segfault when failing to read random numbers. + Reformat the code using astyle. + Fix warnings from GCC about VLAs. + Fix warnings from autoconf. + Fix compiler warnings. + Don't compile support for Device=fd on platforms that do not support UNIX sockets. + Fix the check for sys/un.h. + Log errors when add_edge() fails to insert into the edge trees. + Fix for the event loop on Windows. + Don't try to forward packets to a node we don't have a key for. + Fix spelling errors. + Fix usage of @code and @samp commands. + Update copyright notices. Update THANKS. - Backport tinc 1.1's str2net() function. - Update THANKS. - Fix spelling errors found by codespell. - Reformat all code using astyle. - Add a missing check for a pathname being too long. - Releasing 1.0.36. + Releasing 1.1pre18. -Rosen Penev (2): - Fix compilation when OpenSSL has no ENGINE support - Fix compilation without deprecated OpenSSL APIs +Fabian Maurer (2): + Fix interface spelling + Generate tinc-up.bat on windows -Quentin Rameau (1): - Double-quote nodes in graphviz network file +Ilia Pavlikhin (2): + Fix infinity loop when network address and + Add Subnet checking to tinc cli + +Maciej S. Szmigiero (2): + Revert "Work around a GCC bug that causes inet_checksum() to give wrong results." + Fix strict aliasing violation in inet_checksum() + +pacien (2): + fd_device: allow fd to be passed through a unix socket + tincctl: restrict umask argument for FORTIFY + +Aaron LI (1): + Use auto-clone device /dev/{tun,tap} as default on FreeBSD/DragonFly + +Andreas Rammhold (1): + fix: use EVP_DecryptUpdate while decrypting + +Rosen Penev (1): + fix compilation without deprecated OpenSSL APIs + +Shengjing Zhu (1): + Fix manpage mdoc syntax Werner Schreiber (1): Fix segfault when dest->mtu is 0. -Version 1.0.35 October 05 2018 +iczero (1): + Fix tinc-up generation on windows + +leptonyu (1): + fix macos build + +Version 1.1pre17 October 08 2018 ------------------------------------------------------------------------ -Guus Sliepen (12): - Prevent oracle attacks (CVE-2018-16737, CVE-2018-16738) - Prevent a MITM from forcing a NULL cipher for UDP (CVE-2018-16758) - Check the return value from snprintf(). - Check the return values from BN_hex2bn() and RAND_load_file(). +Guus Sliepen (17): + Remove the ping test. + Add missing item and attribution to NEWS. + Merge remote-tracking branch 'volth/release-1.1pre16-rtt' into 1.1 + Print UDP RTT on its own line. + Avoid treating compressed MTU probes as having a negative length. + Remove address cache debug messages printed to stderr. + Enable AutoConnect by default. + Prevent oracle attacks in the legacy protocol (CVE-2018-16737, CVE-2018-16738) + Add a test for backwards compatibility with the legacy protocol. + Fix compiler warnings. + Fix warnings from the Clang static analyzer. Fix all warnings when compiling with -Wall -W -pedantic. - Fix two small memory leaks. - Don't check for NULL-pointers before calling free(). - Update THANKS. - Fix checks for Cygwin-related macros. Fix spelling errors. + Don't check for NULL-pointers before calling free(). Update README and links to required libraries. - Releasing 1.0.35. + Update THANKS. + Releasing 1.1pre17. -AMRI Amine (1): - Fixing typo +volth (3): + keep track of round trip times of UDP pings + expose traffic stats to 'tinc info ___' and 'tinc dump nodes' + minor Rafael Sadowski (1): OpenBSD has a proper tap device. -Version 1.0.34 June 12 2018 +Version 1.1pre16 June 12 2018 ------------------------------------------------------------------------ -Guus Sliepen (10): - Add missing thanks to the NEWS message. - Fix building documentation when using OpenBSD's make. - Fix #ifdefs that were broken due to commit d178b58. - Don't use SOL_IP and SOL_IPV6. - Make systemd service file handling identical to tinc 1.1. - Rename distro/ to systemd/. - Document how to enable tinc at boot time using systemd. - Fix all spelling errors found by codespell. - Properly implement tinc.texi's dependency on tincinclude.texi. - Releasing 1.0.34. - -Maximilian Stein (1): - Fix SEGFAULT when trying to connect to IPv6 peer in non-IPv6 environment - -wangliushuai (1): - Remove redundant 'break'. - -Version 1.0.33 November 04 2017 ------------------------------------------------------------------------- - -Guus Sliepen (31): - Udpate THANKS. - Fix a potential memory leak. - Use AC_CONFIG_MACRO_DIR(). - Give absolute path for #include to AC_CHECK_HEADERS(). +Guus Sliepen (54): + Update THANKS. Prepare for automatic code formatting using Artistic Style. - Never remove items from cmdline_conf. - Use stack-allocated strings for temporary filenames. - Fix a few minor memory leaks. Remove unused/obsolete checks from configure.ac. - Use getcwd() instead of get_current_dir_name(). - Remove xmalloc.c, backport xalloc.h from tinc 1.1. Update all header guards. Convert sizeof foo to sizeof(foo). Reformat all code using astyle. - Don't call ERR_remove_state(). - Unconditionally include stdbool.h and inttypes.h. - Remove more obsolete autoconf checks. - Remove obsolete m4/README. - Fix some "make distcheck" errors. + Ensure "make distcheck" really runs without errors. Add some information about the requirements of a chroot environment. - Handle tun/tap device returning EPERM or EBUSY. Disable PMTU discovery when TCPOnly is used. - Fix all -Wall -W compiler warnings. - Realign comments. + Only forward SPTPS packets if Forwarding = internal. + If we are using libncurses, also try to link with libtinfo. + Don't log errors when autoconnecting fails and debuglevel is 0. Remove unused functions. Ensure all parameters have names in header files. Support autoconf's --runstatedir option. Const correctness. - Fix compilation errors when --enable-uml is used. Update THANKS. - Releasing 1.0.33. + Fix building documentation when using OpenBSD's make. + Assume all IPPROTO_* macros exist. + Document the --batch option. + Don't warn about empty lines in invitation files. + Document that invitation files MUST always start with Name = ... + Add missing newlines to some error messages. + Merge remote-tracking branch 'dechamps/ipip' into 1.1 + Remove hardcoded paths from systemd unit files. + Set default systemd unit path to ${libdir}/systemd/system. + Ensure the sptps-basic test doesn't fail during make distcheck. + Update .gitignore. + Don't include generated files into the tarball. + Document how to enable tinc at boot time using systemd. + Fix all spelling errors found by codespell. + Add a cache of recently seen addresses. + Fix calling freeaddrinfo() on the wrong pointer. + Reformat all code using astyle. + Add code coverage testing support. + Reduce memory allocations due to zlib's uncompress(). + Reduce memory allocations due to HMAC() and EVP_MD_*(). + Document the control protocol. + Update the documentation of the control protocol. + Unconditionally remove timeouts from the queue before calling the callback. + Revert "Unconditionally remove timeouts from the queue before calling the callback." + Work around a GCC bug that causes inet_checksum() to give wrong results. + Try to process all pending events after select(). + Fix compatibility with LibreSSL and OpenSSL < 1.1. + Reformat all code using astyle. + Ensure we call CloseServiceHandle() in case of errors. + Warn if we cannot reload the tincd when creating an invitation. + Properly implement tinc.texi's dependency on tincinclude.texi. + Prevent an infinite loop in get_recent_address(). + Fix invitation tests if port 655 is available. + Add the ability to set a firewall mark on sockets. + Reformat all code using astyle. + Remove the wxPython GUI. + Releasing 1.1pre16. -Rafael Sadowski (1): - fix tinc.conf for OpenBSD +Todd C. Miller (9): + Fix parsing of -b flag + Replace remaining sizeof foo with sizeof(foo). + Add some missing freeaddrinfo() calls to avoid leaking memory. + WSAEVENT is a pointer, so we cannot simply return the different of two + In device_issue_read() there is no need to reset Offset and OffsetHigh + Fix a use-after-free bug in get_recent_address() and two related issues. + Fix heap corruption on Windows exposed by the use-after free fix. + In device_handle_read() we need to reset the read event on error or + Call WSAWaitForMultipleEvents() in a loop until we have checked all events. -nemunaire (1): - Allow compilation from a build directory +Etienne Dechamps (5): + Support MSS clamping for IP in IP (RFC 2003) packets. + Fix AC_CHECK_DECLS usage in openssl.m4. + Fix "void function should not return void expression" warning. + Fix "use of GNU empty initializer extension" warning. + Move ResetEvent() call before ReadFile(). -Version 1.0.32 September 02 2017 +Daniel Lublin (1): + doc: there is, not their is + +Gjergji (1): + fix service removal. + +Mike Sullivan (1): + Fix handling partial SPTPS messages in sptps_test. + +Oliver Freyermuth (1): + Fix compiling when support for UML sockets is enabled. + +Version 1.1pre15 September 02 2017 ------------------------------------------------------------------------ -Guus Sliepen (13): - Don't dereference myself->incipher if it's NULL. - Merge remote-tracking branch 'VittGam/master' +Guus Sliepen (56): + Preserve IPv6 scope_id in edges. + Fix the previous commit. + Ensure compatibility with OpenSSL 1.1.0. + Add -Wall to CFLAGS. + Check return value of RSA_generate_key_ex(). + Use EVP_MD_CTX_destroy() instead of _free(). + Force nul-termination of strings after vsnprintf(). + Fix warnings from the Clang static analyzer. + Fix potential memory leaks found by the Clang static analyzer. + Add missing m4 files. + Fix compiling with OpenSSL < 1.1.0. + Log warnings about dropped packets only with debug level 5 or higher. + Use AES256 and SHA256 by default for the legacy protocol. + Enforce maximum amount of bytes sent/received on meta-connections. + Fix potential segfault in the replacement vasprintf() function. + Don't build sptps_* binaries by default. + Remove the description of the LocalDiscoveryAddress option from the manual. + Use free_known_addresses() to free memory allocated by get_known_addresses(). + Add missing #defines used by fd_device.c. + Don't try to use kill() on Windows. + Merge remote-tracking branch 'dechamps/sleep' into 1.1 + Put script environment creation/deletion in functions. + Add DEBUG environment variable for scripts. + Use unique ports for all tests. + Remove superfluous sleep command in invite-join test. + Add the invite-offline test. + Update .gitignore. + Add the scripts test. + Ensure proper logging in the invite-offline test. + Use 127.0.0.1 instead of localhost to ensure tests are reproducible. + Ensure sptps_keypair and sptps_test get build for make check. Use /dev/udp instead of /dev/ip on Solaris. Use getmsg()/putmsg() instead of read()/write() on Solaris. - Fix Solaris DeviceType = tap in router Mode. - Bind outgoing TCP sockets. + Ensure tests compile on *BSD. + Make sure realname is always initialized. + Fix compiler warnings on *BSD. + Fix segfault when adding environment variables. + Fix tests on *BSD. + Add missing tinc stop command to the scripts test. + Remove dead stores. + Add field widths to sscanf() calls. + Fix some minor issues found by cppcheck. + Remove unused add_scalar function. Move logging of "would block" messages to debug level 4. Set KillMode=mixed in the systemd service file. - Don't forget about outgoing connections on host file read errors. - Fix Proxy = exec. - Set status.proxy_passed early for Proxy = exec. - Don't try to bind Proxy = exec sockets to an address. - Releasing 1.0.32. + Add configurable experation time for invitations. + Store the invitation data after a succesful join. + Forward-port tinc 1.0's handling of device errors. + Make autoconnect try to heal network splits. + Add missing break statements. + Force IPv4 for sptps-basic.test. + Fix a compiler warning. + Fix a file descriptor leak when using an invitation. + Ensure packet priority is cleared when sending PMTU probe replies. + Drop h and hh length modifiers from printf format strings. + Releasing 1.1pre15. + +Etienne Dechamps (7): + Fix error handling when setting up the UDP socket. + Fix crash on Windows when a socket is available for both write and read. + On Windows, don't cancel I/O when disabling the device. + Fix edge local addresses not being set when connections are established. + Fix edge updates containing local address changes. + Clarify the flow of add_edge_h(). + Fix address memory leaks in add_edge_h(). + +thorkill (4): + Send PKT_PROBE only when handshake has been done already. + Prevent tincd from sending packets to unexpecting nodes + Sanitize input in id_h - prevent integer overflows + Fix NULL pointer dereference in send_udp_info + +Sean McVeigh (2): + check for daemon pid existence before trying to connect to the control socket, and clean up stale files otherwise. + fix check in cmd_pid() for failure to connect to tincd + +Vittorio G (VittGam) (2): + fsck: Fix ed25519 public key reading, and fclose usage. + tincctl: Avoid falling back to 1024 bits RSA key generation when an invalid key size is specified. + +Dennis Lan (1): + Fix typo in src/upnp.c. + +Pacien TRAN-GIRARD (1): + Add fd_device + +Roman Savelyev (1): + Fix lost pointer trails in get_known_addresses(). Vittorio Gambaletta (VittGam) (1): route: Support ToS/DiffServ priority inheritance when routing IPv6 packets. -Version 1.0.31 January 15 2017 +lemoer (1): + Added comments and unfold deep "if"-construct in timeout_handler + +pacien (1): + Add LogLevel config option + +volth (1): + Avoid infinite loop on EBADFD + +Version 1.1pre14 May 01 2016 ------------------------------------------------------------------------ -Guus Sliepen (1): - Releasing 1.0.31. +Guus Sliepen (2): + Revert "Remove tinc.service, it is not necessary." + Releasing 1.1pre14. -Élie Bouttier (1): - Remove ExecStop in tinc@.service - -Version 1.0.30 October 30 2016 +Version 1.1pre13 April 30 2016 ------------------------------------------------------------------------ -Guus Sliepen (11): - Allow non-empty lines after status code from a HTTP proxy. - Fix proxy reply parsing broken by the previous commit. - Log only the first line of a proxy request rejection message. - Delay sending the real ID request until after a proxy request is granted. - Use AES256 and SHA256 by default, also for the meta-connections. - Enforce maximum amount of bytes sent/received on meta-connections. - Fix bit shifting arithmetic so the code actually does what the last commit message says. - Really fix byte budget calculation. - Use AES in CTR mode instead of OFB mode for meta-connections. - Use CFB mode for meta-connections to improve security. - Releasing 1.0.30. +Guus Sliepen (4): + Fix BSD tun device support. + Remove tinc.service, it is not necessary. + AutoConnect now only chooses from nodes for which we know an address. + Releasing 1.1pre13. -Version 1.0.29 October 09 2016 +Version 1.1pre12 April 24 2016 ------------------------------------------------------------------------ -Guus Sliepen (11): - Preserve IPv6 scope_id in edges. - Ensure compatibility with OpenSSL 1.1.0. - Add -Wall to CFLAGS. - Check return value of RSA_generate_key_ex(). - Force nul-termination of strings after vsnprintf(). - Log warnings about dropped packets only with debug level 5 or higher. - Add a copy of ax_append_flag.m4. - Add ax_require_defined.m4. - Fix possibly unitialized variable. - Fix compiler warnings about format string errors on BSD. - Releasing 1.0.29. - -Version 1.0.28 April 10 2016 ------------------------------------------------------------------------- - -Guus Sliepen (8): - Fix compiling bsd/device.c on systems without utun. - Really remove use of __DATE__ and __TIME__ to facilitate reproducible builds. - Add systemd service files. - Update .gitignore. - Ensure the service files are in the tarball. - Explicitly mention that LibreSSL can be used as well. - Update links in the documentation. - Releasing 1.0.28. - -Version 1.0.27 April 10 2016 ------------------------------------------------------------------------- - -Guus Sliepen (26): - Add missing AM_PROG_CC_C_O to configure.ac. - Attribution for various contributors. - Update "now" after connect() when making outgoing connections. - Add ability to use proxies to connect to hostnames when there is no nameserver. +Guus Sliepen (166): + Allow tinc to be compiled without OpenSSL. + Add missing nolegacy/crypto.c and prf.c. + Fixes for bugs in src/Makefile.am and tincctl.c introduced by cfe9285adf391ab66faeb5def811fe08e47a221a. + Fix indentation and some whitespace issues. + Use void pointers for opaque data blobs in the SHA512 code. + Use global "now" in try_udp() and try_mtu(). + Remember whether we sent our key to another node. + Try to clarify the new code in net_packet.c a bit. + Correctly estimate the initial MTU for legacy packets. + Fix size of type 2 probe replies. + Proactively send our own key when we request another node's key. + Don't send probe replies if we don't have the other's key. + Fix segfault when sptps_test cannot open the key files. + Always keep UDP mappings alive for nodes that also have a meta-connection. + Immediately send our key when a meta-connection is established. + Only send small packets during UDP probes. + Remove RTT and packet loss estimation code. + Send MTU probes only once every PingInterval. + Move detection of PMTU decrease to try_mtu(). + Keep track of the largest UDP packet size received from a node. + Move UDP probe reply code into its own function. + Send the size of the largest recently received packets in type 2 probe replies. + Send gratuitous type 2 probe replies. + Improve packet source detection. + Add the "fsck" command to the CLI. + Always call res_init() before getaddrinfo(). + Make "tinc add" idempotent. + Document that --force should precede commands. + Suppress warnings about parsing Ed25519 keys when they are not present. + Merge remote-tracking branch 'dechamps/sptpsabort' into 1.1 + Merge remote-tracking branch 'seehuhn/1.1' into 1.1 + Fix the case where we detach and use --logfile. + --syslog and --logfile are mutually exclusive. + Merge remote-tracking branch 'dechamps/staticfix' into 1.1 + Merge remote-tracking branch 'dechamps/fsckwin' into 1.1 + Merge remote-tracking branch 'dechamps/winmtu' into 1.1 + Merge remote-tracking branch 'dechamps/windevice' into 1.1 + Always call res_init() before getaddrinfo(). + Merge remote-tracking branch 'dechamps/wintapver' into 1.1 + Allow one-sided upgrades to Ed25519. + Fix a possible segmentation fault during key upgrades. + Don't log an error message when receiving a TERMREQ. + Fix typo 0fda572c88d02b0b200ef81d72cc4da594fa0e38 that prevented some errors from being logged. + Remove "release-" from displayed git version. + Don't include build-time generated version_git.h in the tarball. + Really remove "release-" from the git-derived version string. + Fix invitations. + Fix receiving UDP packets from tinc 1.0.x nodes. + Use AF_UNSPEC instead of AF_UNKNOWN for unspecified local address in add_edge_h(). + Be more liberal accepting ADD_EDGE messages with conflicting local address information. + Try all addresses for the hostname in an invitation URL. + Let sockaddr2str() handle AF_UNSPEC addresses. + Don't send local_address in ADD_EDGE messages if it's AF_UNSPEC. + Merge remote-tracking branches 'dechamps/sptpsrestart' and 'dechamps/keychanged' into 1.1 + Remove info-in-builddir option from AM_INIT_AUTOMAKE(). + Fix src/Makefile.am for *BSD. + Add newline at end of precomp_data.h and sc.h. + Add source of SPTPS errors to log messages. + Don't log seqno failures in sptps_verify_datagram(). + If LOCALSTATEDIR is inaccessible, store the pid and socket files in the configuration directory. + Quit with an error message if ioctl(TUNSETIFF) fails. + Add "list" as an alias for "dump" in the CLI. + Allow dumping a list of outstanding invitations. + Allocate temporary filenames on the stack. + Fix check for LOCALSTATEDIR accessibility for the CLI. + Ensure "tinc start" knows if the daemon really started succesfully. + Don't write log messages to the umbilical pipe if we don't detach. + Use socketpair() instead of pipe() for the umbilical. + Set the CLOEXEC flag on the umbilical socket. + Update copyright notices. + Fix missing return value caused by the previous commit. + Fix autoconf check for function attributes. + Fix warnings about missing return value checks. + Fix receiving SPTPS data in sptps_speed and sptps_test. + Fix alignment of output of sptps_speed. + Fix crash is sptps_logger(). + Don't #include OpenSSL headers when compiling without OpenSSL. + Coalesce two if statements that check for the same thing. + Call sockaddrfree(&e->local_address) in free_edge() instead of exit_edges(). + Fix undefined behaviour when left-shifting signed integers. + Remove unused code that caused warnings about an uninitialized variable. + Use AC_CONFIG_MACRO_DIRS([m4]). + Make subnet caches static. + Fix the PRF function when compiling without OpenSSL. + Use AC_CONFIG_MACRO_DIR() instead of _DIRS(). + In sssp_bfs(), never try to update myself. + Add -I m4 back to ACLOCAL_AMFLAGS. + Optionally install systemd service files. + Replace bare if statements with AS_IF in configure.ac. + Fix struct node_status_t. + Fix a few memory leaks in the CLI found by AddressSanitizer. + Avoid undefined behavior. + Update THANKS file. + Don't leave dead outgoing_t's in the outgoing_list. + list_delete() already free()s the deleted element. + Add support for recvmmsg(). + Use static buffers for recvmmsg(), initialize them only as needed. Only add a reflexive address when we're sure it's working. - Fix compatibility with TAP-Win32 9.0.0.21 and later. - Fix warnings from the Clang Static Analyzer. + Merge remote-tracking branch 'mweinelt/tinc-gui' into 1.1 + Add the ability to sign and verify files. + Update .gitignore. + Only check for -fno-strict-overflow if -fwrapv does not work. + Use nostdinc instead of overriding DEFAULT_INCLUDES. Improve performance of edge updates. + Fix forwarding of edge updates. Clarify that scripts are called synchronously. Small fixes for the documentation. Add warnings for bad combinations of Device and Interface. - Fix forwarding of edge updates. + Fix for botched cherry-pick commit 60fb230. + Fix typo. Don't compile getopt*.c if the system provides getopt_long(). Update .gitignore. Update THANKS. Use iface instead of interface. - Update copyright notices. - Remove use of __DATE__ and __TIME__ to facilitate reproducible builds. - Cast 0xff to char before comparing it to another char. - Get rid of a warning when compiling tinc using MinGW. - Every BSD flavor has a tap device nowadays. - Use devname() if available to support devfs cloning on BSD. - Use SIOCGIFADDR on BSDs that support it. + Support ToS/DiffServ for IPv6 meta and UDP connections. + Fix --logfile without a filename on Windows. + Never call putenv() with data on the stack. + Update "now" after connect() when making outgoing connections. + Update support for BSD tun/tap devices, add support for OS X utun interfaces. + Explicitly mention that LibreSSL can be used as well. + Update links in the documentation. Enable silent builds by default. - Add support for OS X utun interfaces. - Releasing 1.0.27. + Really don't compile getopt*.c if the system provides getopt_long(). + Remove elliptic curve stubs from gcrypt/, add PRF implementation. + Update .gitignore. + Make text files Markdown-compatible. + Remove checks for headers and functions that are in C99. + Fix compiling under MinGW. + Replace usleep() with nanosleep(). + Use getcwd() instead of get_current_dir_name(). + Fix typo in Makefile.am. + Fix version_get.h generation on BSD. + Remove checks for non-C99 compliant compilers. + Remove support for Windows 2000 and anything that doesn't support getaddrinfo(). + Make some platform-specific header checks conditional. + Add version_git.h and sample-config.tar.gz to CLEANFILES. + Don't assume sa.sa_family is a short int. + Remove use of strcpy() and sprintf(). + Don't use HAVE_SYSTEM, the autoconf check was removed. + Fix a non-working cast to get rid of a compiler warning. + Fix generation of version_git.h for some versions of BSD make. + Fix some compiler warnings from MinGW. + Fix conditional checking of tun/tap headers on DragonFly BSD. + Fix crash at startup when Device is not specified on OS X. + Stop using SOL_TCP, SOL_IP and SOL_IPV6. + Document how invitation files work. + Generate a tinc-up script from an invitation. + Move some stray #includes. + Allow gateways to be specified for routes. + Fix gateway parsing in invitation files. + Fix compiler warnings. + Add a test for tinc-up creation from invitations. + Chdir() to the configuration directory instead of /. + Use ifconfig_header(). + Add stricter checks for netnames. + Handle special characters in sptps_test only if the --special option is given. + Don't call terminate_connection(myself->connection). + Speed up AutoConnect at startup. + Fix the "network" command in tinc shell. + Move documentation of invitations to the manual. + Have "tinc fsck" recognize Ed25519PublicKey statements. + Fix possible read of freed memory when verifying the signature of a file. + Fix a compiler warning on Windows. + Fix starting tinc as a service on Windows. + Don't check file permissions on Windows during fsck. + Releasing 1.1pre12. + +Etienne Dechamps (72): + Clarify the send_mtu_probe() function. + Add the try_tx() function. + Move try_sptps() closer to try_tx(). + Add UDP discovery mechanism. + Move responsibility for local discovery to UDP discovery. + Remove PMTU discovery code redundant with UDP discovery. + Move PMTU discovery code into the TX path. + Move try_mtu() closer to try_tx(). + Fix MTU as soon as possible. + Use -1 to identify the post-initial MTU discovery state. + Send one MTU probe at a time. + Remove bandwidth estimation code. + Use a smarter algorithm for choosing MTU discovery probe sizes. + Adjust MTU probe counts. + Don't send MTU probes smaller than 512 bytes. + Add IP_MTU-based maxmtu estimation. + Fine-tune the MTU discovery multiplier for the maxmtu < MTU case. + Recalculate and resend MTU probes if they are too large for the system. + Use a different UDP discovery interval if the tunnel is established. + Fix typo in logging statement. + Fix dynamic UDP SPTPS relaying. + Fix UDP/MTU discovery in intermediate SPTPS UDP relays. + Don't abort() willy-nilly in SPTPS code. + Add UDP_INFO protocol message. + Add MTU_INFO protocol message. + Throttle the rate of UDP_INFO messages. + Throttle the rate of MTU_INFO messages. + Don't send UDP probes past static relays. + Fix invalid getuid() call on Windows. + Fix HAVE_DECL_RES_INIT conditionals. + Make sure packet header structures are correctly packed on Windows. + When disabling the Windows device, wait for pending reads to complete. + Fix Windows device asynchronous write behavior. + Set the default for UDPRcvBuf and UDPSndBuf to 1M. + Increase the ReplayWindow default from 16 to 32. + Log TAP-Windows driver version on startup. + Warn about performance if using TAP-Windows >=9.21. + Use git description as the tinc version. + Use git describe to populate autoconf's VERSION. + Remove explicit distribution rules for m4 scripts. + Add support for out-of-tree ("VPATH") builds. + When relaying, send probes to the destination, not the source. + Use the correct originator node when relaying SPTPS UDP packets. + Expose the raw SPTPS send interface from net_packet. + Try to use UDP to relay SPTPS packets received over TCP. + Rename REQ_SPTPS to SPTPS_PACKET. + Only read one record at a time in sptps_receive_data(). + Introduce raw TCP SPTPS packet transport. + Prevent SPTPS key regeneration packets from entering an UDP relay path. + Trivial: make sptps_receive_data_datagram() a little more readable. + Proactively restart the SPTPS tunnel if we get receive errors. + Don't send KEY_CHANGED messages if we don't support the legacy protocol. + Make sure the MIN() macro is defined. + Don't pollute the system header directory namespace. + Fix SPTPS condition in try_harder(). + Don't parse node IDs if the sending node doesn't support them. + Fix direct UDP communciation with pre-relaying 1.1 nodes. + Fix crashes when trying unreachable nodes. + Don't set up an ongoing connection to myself. + Fix wrong format string type in send_sptps_tcppacket(). + Fix invalid pointer use in get_my_hostname(). + Don't try to relay packets to unreachable nodes. + Protect against callbacks removing items from the io tree. + Use a splay tree for node UDP addresses in order to avoid collisions. + Revert "Cache node IDs in a hash table for faster lookups." + Make sure the packet source MAC address is always set. + Add a new optional dependency on the miniupnpc library. + Add UPnP support to tincd. + Allow tinc to be built with miniupnpc on Windows. + Try to ensure we build correctly against various libminiupnpc versions. + Don't unset validkey when receiving SPTPS handshakes over ANS_KEY. + Add upnp.h to tincd SOURCES. + +thorkill (8): + Fixed 2 leaks in setup_myself() + Cleanup edges stored in edge_weight_tree on exit + Cleanup local_address in protocol_edge.c + Removed double break; + Included missing names.h + Make sure we do not allocate new edge when talking to old nodes and the same edge already exists + Prevent tinc from forgeting e->local_address + Do not access e->to->prevedge if not defined Vittorio Gambaletta (VittGam) (6): Fix DecrementTTL option. @@ -221,201 +582,947 @@ Vittorio Gambaletta (VittGam) (6): s/broadcast_packet_helper/route_broadcast/ Remove forward declaration for do_decrement_ttl. -LunarShaddow (3): +Martin Weinelt (5): + tinc-gui: Reformat codebase according to PEP8 + tinc-gui: Update Node object to correctly parse responses + tinc-gui: Fix GetListCtrl method name in SuperListCtrl + tinc-gui: Use ArgumentParser, default to python2 + tinc-gui: Properly initialize class attributes for VPN in __init__ + +Sven-Haegar Koch (3): + Fixed variables.test testsuite after 'Make "tinc add" idempotent.' change. + Let sockaddr2hostname() handle AF_UNSPEC addresses. + Fix check for public key in invite-join.test. + +Florian Klink (2): + (read|append)_config_file: log open errors as LOG_DEBUG + setup_outgoing_connection: log to LOG_DEBUG on if no known address + +LunarShaddow (2): fix typo re-arrange include sequence to avoid a mingw introduced bug. - Proofing README. -Florian Weik (1): - Fix NAME variable in subnet-* scripts for local subnets. - -Nathan Stratton Treadway (1): - Fix invalid checksum generation. - -Version 1.0.26 July 05 2015 ------------------------------------------------------------------------- - -Guus Sliepen (14): - Use VittGam's real name. - Attribution for Saverio Proto. - Always call res_init() before getaddrinfo(). - Fix --logfile without a filename on Windows. - Never call putenv() with data on the stack. - Return non-zero exit code when encountering configuration errors during startup. - Fix autoconf check for function attributes. - Fix spelling of FORTIFY_SOURCE. - Update copyright notices. - Attribution for various contributors. - Only check for -fno-strict-overflow if -fwrapv does not work. - Fix unputenv() on Windows. - Don't try to call res_init() if ./configure told us it doesn't exist. - Releasing 1.0.26. +Dato Simó (1): + Fix typo in tinc.texi. Jo-Philipp Wich (1): fix musl compatibility -Version 1.0.25 December 22 2014 +Jochen Voss (1): + Add a new --syslog option for tincd. + +Nathan Stratton Treadway (1): + Fix invalid checksum generation. + +Pierre Emeriaud (1): + Fix typo in tincctl help. + +xentec (1): + Fix compile errors introduced in cfe9285adf391ab66faeb5def811fe08e47a221a + +Version 1.1pre11 December 27 2014 ------------------------------------------------------------------------ -Guus Sliepen (7): - Fix date of last NEWS entry. - Remember ToS/Diffserv priority for each socket individually. - Attribution for various contributors. - Automatically choose a tap device on Mac OS X when using switch Mode. - Update documentation for Mac OS X. - Check whether res_init() really lives in libresolv. - Releasing 1.0.25. +Etienne Dechamps (68): + Move Solaris if_fd to local scope. + Make device close cleaner. + Cleanly remove the device FD from the event loop before closing it. + Add DeviceStandby option to only enable the device when nodes are reachable. + Make DeviceStandby control network interface link status on Windows. + Fix Windows includes. + Fix errno references when handling socket errors. + Protect against spurious connection events. + Fix connection event error handling. + Use native Windows events for the event loop. + Make the event loop expose a Windows event interface. + Use a Windows event to stop tinc when running as a service. + Remove the TAP-Win32 reader thread. + Add local address information to edges. + Use edge local addresses for local discovery. + Remove broadcast-based local discovery mechanism. + Enable LocalDiscovery by default. + Implement sptps_verify_datagram(). + Make broadcast addresses configurable. + Make IPv4 multicast space 224.0.0.0/4 broadcast by default. + Regenerate build date and time every time tinc is built. + Use git description as the tinc version. + Rewrite, fix and improve str2net(). + When printing MAC addresses, always use trailing zeroes. + Don't print subnet prefix lengths and weights for one-host subnets. + Canonicalize IPv6 addresses as per RFC 5952 before printing them. + Fix tinc event loop reentrancy from timeout handlers. + Make sure myport is set correctly when running with Port = 0. + Fix event loop io tree inconsistency on Windows. + Fix a typo (FORTIFY_SOURCE). + Handle the "no local address" case in send_sptps_data(). + Don't initialize outpkt to an unused value. + Remove redundant connection_t::status.active field. + Only declare the origpriority variable if we support priority. + Remove an unnecessary pointer dereference in execute_script(). + Fix callback signature for TAP-Win32 device_handle_read(). + Remove unused variable in TAP-Win32 setup_device(). + Remove unused device stats variables. + Resolve KEY_EVENT conflict between Windows and ncurses. + Check if devops is valid before closing the device. + Shutdown cleanly when receiving a Windows console shutdown request. + Fix "tinc start" on Windows when the path contains spaces. + Improve subprocess behavior in tinc start command. + Add documentation about using system-assigned ports. + Verify seqno early in sptps_verify_datagram(). + Add a non-interactive mode to tinc commands. + Only read from TAP-Win32 if the device is enabled. + Handle TAP-Win32 immediate reads correctly. + Clarify copyright ownership for code authored by Etienne Dechamps. + Remove Google from the list of copyright owners. + Fix undefined HOST_NAME_MAX on Windows. + Don't enable the device if the reachable count is zero. + Fix wrong identifier in SO_NOSIGPIPE call. + Fix default TAP device on Darwin. + Ignore the Interface option if device rename is impossible. + Fix default device path selection on BSD. + Preemptively mirror REQ_PUBKEY messages from nodes with unknown keys. + Fix protocol version check for type 2 MTU probe replies. + Invalidate UDP information on address changes. + Introduce node IDs. + Change vpn_packet_t::seqno from uint32_t to uint8_t[4]. + Prepend source node ID information to UDP datagrams. + Add UDP datagram relay support to SPTPS. + Don't send MTU probes to nodes we can't reach directly. + Make sure to discover MTU with relays. + Query the Linux device for its MAC address. + Don't spontaneously start SPTPS with neighbors. + Use plain old PACKET for TCP packets sent directly to a neighbor. -Borg (3): - Fixed scripts calling under Win32. - Get MAC of TAP device. - Fixed tinc-up script calling on Win32. +Guus Sliepen (68): + Really fix compiling under Windows. + Add missing attribution for 1.1pre10 to the NEWS file. + Add "network" command to list or switch networks. + Rewind the file before trying to use PEM_read_RSA_PUBKEY(). + Handle a disconnecting tincd better. + Fix return value of b64encode(). + Use Ed25519 keys. + Properly initialize buffers. + Merge branch '1.1-ed25519' into 1.1 + Use the ChaCha-Poly1305 cipher for the SPTPS protocol. + sptps_test: allow using a tun device instead of stdio. + Put brackets around IPv6 addresses in invitation URL, even if there is no port number. + Nexthop calculation should always use the shortest path. + Fix compiler warnings. + Change AutoConnect from int to bool. + Use void pointers to opaque buffers. + Add missing closedir(). + Fix a crash when we have a malformed public ECDSA key of another node. + Fix PMTU discovery via datagram SPTPS. + Add sanity checks when generating new RSA keys. + Rename ECDSA to Ed25519. + Implement a PEM-like format for Ed25519 keys. + Allow Cipher and Digest "none". + Fix base64 decoding of Ed25519 keys. + Return non-zero exit code when "tinc get" does not find the requested variable. + Unconditionally return non-zero exit code when "tinc del" does not find the requested variable. + Remove the warnings when IP_DONTFRAGMENT/IPV6-DONTFRAG is not supported. + Merge branch 'winevents-clean' of https://github.com/dechamps/tinc into 1.1 + Give getsockopt() a reference to a socklen_t. + Fix compiler warnings. + Fix segmentation fault when dumping subnets. + Fix incorrect format qualifiers. + Reserve legacy active bit in connection_status_t. + Fix a potential file descriptor leak. + Fix unsafe use of strncpy() and sprintf(). + Merge branch 'winwarnings' of https://github.com/dechamps/tinc into 1.1 + Merge branch 'ctrl' of https://github.com/dechamps/tinc into 1.1 + Merge branch 'tincstart' of https://github.com/dechamps/tinc into 1.1 + Merge branch 'keysegfault' of https://github.com/dechamps/tinc into 1.1 + Revert "Use git description as the tinc version." + Fix compiler warnings. + Check validity of Ed25519 key during an upgrade. + Log an error message with the node's name when receiving bad SPTPS packets. + Better log messages when we already know the peer's key during an upgrade. + Add an explicit hash_delete() function. + Cache node IDs in a hash table for faster lookups. + Avoid memmove() for legacy UDP packets. + Make UDP packet handling more efficient. + Changes that should have been in commit 46fa12e666badb79e480c4b2399787551f8266d0. + Fix segfault when receiving UDP packets with an unknown source address. + Fix reception of SPTPS UDP packets. + Avoid using OpenSSL's random number functions. + Don't pass uninitialized bytes to ioctl(). + Don't use myself->name in device_disable(), it's already freed. + Fix memory leaks found by Valgrind. + Use void pointers for opaque data blobs in the SPTPS code. + Add a variable offset to vpn_packet_t, drop sptps_packet_t. + Merge remote-tracking branch 'groxxda/gui-fixes' into 1.1 + Allow running tinc without RSA keys. + Update THANKS file. + Check whether res_init() really lives in libresolv. + BSD make doesn't like .PHONY .c files. + We don't depend on ECDH functions from OpenSSL anymore. + Linux doesn't like .PHONY .o files. + Remove AES-GCM support. + Better default paths for log and PID files on Windows. + Add BroadcastSubnet and DeviceStandby options to the manual and completion. + Releasing 1.1pre11. + +Sven-Haegar Koch (4): + Fix exit code of "tinc get". + commandline.test: Adding test that fetching non-existing config setting really fails. + Do not disconnect when no ecdsa key is known yet. + Try handling the case when the first side knows the ecdsa key of + +William A. Kennington III (3): + utils: Refactor get_name's functionality into util for global access + utils: Refactor check_id out of protocol for global access + tincctl: Use replace_name to properly replace and validate input hostnames + +Baptiste Jonglez (2): + Clarify man page regarding the IndirectData option + Fix typos in the manual page Alexis Hildebrandt (1): Add support to link against libresolv Mac OS X -Baptiste Jonglez (1): - Use the description from the 1.1 man page for the IndirectData option - -David Pflug (1): - Update README.android - -Jochen Voss (1): - Fix some typos in the manual. - -Tomislav Čohar (1): - Configure minimum reconnect timeouts. - -VittGam (1): - Support ToS/DiffServ priority handling for IPv6 meta and UDP connections. - -Version 1.0.24 May 11 2014 ------------------------------------------------------------------------- - -Guus Sliepen (13): - Remove useless variable 'hard' from try_harder(). - Merge pull request #14 from luckyhacky/master - Add an autoconf check for res_init(). - Nexthop calculation should always use the shortest path. - Fix issues found by Coverity. - Fix warnings found by GCC 4.9. - Fix a few more issues found by Coverity. - Fix a few more issues found by Coverity. - Drop h and hh length modifiers from printf format strings. - Fix a bug that could prevent tinc from starting correctly on Windows. - FIx the autoconf checks for res_init(). - Remove the warnings when IP_DONTFRAGMENT/IPV6-DONTFRAG is not supported. - Releasing 1.0.24. - -Steffan Karger (3): - Use constant time memcmp() when comparing packet HMACs. - Use cryptographically strong random when generating keys. - Check RAND_bytes() return value, fail when getting random fails. - Armin Fisslthaler (1): reload /etc/resolv.conf in SIGALRM handler -Loic Dachary (1): - fix documentation typo +Franz Pletz (1): + tinc-gui: Use /usr/bin/env to resolve path to python -luckyhacky (1): - update to openssl version 1.0.1g due to lack of heartbleed bug in prior version of openssl +Saverio Proto (1): + Fix typo in comment -refs/tags/1.0.23-android-1 March 11 2014 +groxxda (1): + tinc-gui: Don't assign broadcast subnets to any node, fix parsing of Edges, fix diplay of Subnet.weight. + +Version 1.1pre10 February 07 2014 ------------------------------------------------------------------------ -Guus Sliepen (13): +Guus Sliepen (52): + Wrong date for the 1.1pre9 release in the NEWS. + Avoid using BIOs. + Add a benchmark for the SPTPS protocol. + Don't leak memory during the key generation speed test. + Link sptps_speed with -lrt. + Fix segfault when Name = $HOST but $HOST is not set. + Fix typos in the documentation. + Use AES-256-GCM for the SPTPS protocol. + Fix sending empty SPTPS records. + Clean up child processes from proxy type exec. + Make sptps_test less verbose by default. + Fix sending bulk data starting with a newline. + Fix two warnings from Clang's static analyzer. + Remove an unused variable. + Make LocalDiscovery work for SPTPS packets. + Allow "none" for Cipher and Digest again. Mention in the manual that multiple Address staments are allowed. If no Port is specified, set myport to actual port of first listening socket. - Enable compiler hardening flags by default. Update support for Solaris. Include for PATH_MAX. Stricter check for raw socket support. + Avoid using a variable named "sun". Solaris doesn't like it. Use hardcoded value for TUNNEWPPA if net/if_tun.h is missing on Solaris. - Fix incorrectly merged bits from 80cd2ff73071941a5356555b85a00ee90dfd0e16. + Prefer ncurses over curses. + Don't print device statistics when exiting tinc. + Allow running without ECDSA keys If ExperimentalProtocol is not explicitly set. + Give full path to unconfigured tinc-up script. + Don't print an error when no ECDSA key is known for a node using the legacy protocol. + Remove erroneous warning about SPTPS being disabled. + Enable compiler hardening flags by default. + Add our own autoconf check for libgcrypt. Don't enable -fstack-protector-all. - Remove or lower the priority of some debug messages. + Fix handling of --with-libgcrypt. Clarify StrictSubnets. + Update the documentation of the tinc command. + Add index entries for the CLI commands. + Let tinc-gui use correct address family when connecting to tincd via TCP. + Document clearly that tinc depends on curses and readline libraries. + Document that 1.1 uses AES-256 in GCM mode. + Add the ListenAddress option. + Test two tinc daemons using network namespaces. + Add missing newlines when copying variables from tinc.conf to an invitation file. + Don't ask questions if we are not running interactively. + Document Weight and also allow it to be set from tinc.conf. + Use addresses learned from other nodes when making outgoing connections. Attribution for various contributors. Handle errors from TAP-Win32/64 adapter in a better way. - -Florent Clairambault (2): - Adding "conf.d" configuration dir support. - Adding some documentation around the /etc/tinc/$NET/conf.d directory. - -Vilbrekin (1): - Update android build instructions. Disable PIE as this is not supported on some devices. - -Version 1.0.23 October 19 2013 ------------------------------------------------------------------------- - -Guus Sliepen (9): - Check for writability when waiting for a socket to finish connecting. - Don't send PING requests on connections which are not active yet. - Fix segfault when Name = $HOST but $HOST is not set. - Fix typos in the documentation. - Modernize the build system. - Get rid of the splay tree implementation. - Add description of IffOneQueue and MaxTimeout to the info manual. - Clean up child processes from proxy type exec. - Releasing 1.0.23. - -Version 1.0.22 August 13 2013 ------------------------------------------------------------------------- - -Guus Sliepen (7): - Better optional argument handling. - Fix a typo. - Set $NAME when calling host-up/down and subnet-up/down scripts. - Don't use vasprintf() anymore on Windows. - Don't echo broadcast packets back when Broadcast = direct. + Attribution for Dennis Joachimsthaler. Update copyright notices. - Releasing 1.0.22. + Fix compiling for Windows. + Check whether OpenSSL has support for GCM. + Releasing 1.1pre10. + +Dennis Joachimsthaler (2): + Fix tinc-gui on Windows. + Ensure tinc-gui running in 64 bits mode can find tinc's 32 bit registry key. + +Florent Clairambault (1): + Adding "conf.d" configuration dir support. + +Version 1.1pre9 September 08 2013 +------------------------------------------------------------------------ + +Guus Sliepen (40): + Stop using EXTRA_DIST in src/Makefile.am. + Remove texi2html rule in docs/Makefile. + Create UNIX socket at the same time as the PID file is created. + Don't force a .bat extension for scripts under Windows. + Fix order of tincd's initialization. + Remove broadcast of KEY_CHANGED message during tinc's initialization. + Bind outgoing sockets again. + Resolve the local host name before generating the invitation file. + Use our own infrastructure for finding out the local node's externally visible host name. + Let a server explicitly send a notification when the invitation protocol succeeded. + Ensure the invitation filenames do not reveal the secret cookie. + Execute scripts when invitations are created or accepted. + Use PATHEXT when checking for the presence of scripts on Windows. + Tell invited node about Mode and Broadcast settings. + Call WSAStartup() in main(). + When generating invitations, handle any order of Port and Adress statements. + Add an option to test datagram SPTPS with packet loss. + Fix CTR mode. + Fix the replay window in SPTPS. + Allow testing the replay window with sptps_test. + Start of a test suite. + Some shells set $_ to an absolute path. + Make sptps_test more easy to work with. + Small fixes for tests. + Add test for import, export and exchange commands. + Fix tincd logfile location when running tests. + Clean up leftover tincd and sptps_test processes. + Send a RELOAD to a running tincd when a new invitation key has been generated. + Slightly relax the connection rate limit for a single address. + Also test whether tinc daemons can connect to each other after import/export. + Add a test for invite and join commands. + Exit value 1 instead of a random non-zero value. + Fix multicast device. + Add two more test scripts. + Don't return zero-length packets when receiving multicast loopback packets. + Test running ping through two tinc daemons. + Automake doesn't like info files being mentioned in CLEANFILES. + Make sure test scripts end up in the tarball. + Don't try to mkdir(CONFDIR) if --config is used. + Releasing 1.1pre9. Etienne Dechamps (1): + Fix broken build with --with-openssl, --with-libgcrypt. + +Version 1.1pre8 August 13 2013 +------------------------------------------------------------------------ + +Guus Sliepen (56): + Don't try to create tinc.conf when using set or add commands. + Modernize the configure script a bit. + Use conditional compilation for device.c. + Use conditional compilation for cryptographic functions. + Rename xmalloc_and_zero() to xzalloc(). + Add generic crypto headers. + Add more __attribute__((malloc)) where appropriate. + Add __attribute__((warn_unused_result)) to crypto functions. + Fix warnings for functions marked __attribute((warn_unused_result)). + Add a few more checks and warnings in the crypto functions. + Enable the SPTPS protocol by default. + Fix check for presence of ECDSA public key for outgoing connections. + Use read_host_config() where appropriate. + Don't free ephemeral ECDH keys twice. + Fix potential NULL pointer dereferences. + Don't try to handle incoming data if sptps_start() has not been called yet. + Enable and fix warnings from automake. + Send a new key when we receive packets from a node we don't have a valid key for. + Annotate the xalloc functions. + Improve base64 encoding/decoding, add URL-safe variant. + Add a newline when logging to stderr in the tinc binary. + Fix port number in pidfile. + Add an invitation protocol. + Better optional argument handling. + Allow the log output to be stopped with control-C in tinc's shell. + Use strerror() instead of gai_strerror() when err == EAI_SYSTEM. + Add the LocalDiscoveryAddress option. + Set $NAME when calling host-up/down and subnet-up/down scripts. + Add connection rate limiting. + Fix warning "Both netname and configuration directory given" on Windows. + Add missing definitions on Windows. + Don't search in local directories for include files. + Don't use vasprintf() anymore on Windows. + Attribution for Etienne Dechamps. + Forbid protocol version rollback. + Allow extra options to be passed to "tinc restart" again. + Honour umask, let temporary key files inherit original's permissions. + Fix compression when using the SPTPS protocol. + Warn when incorrect use of add or set causes variables to be removed. + Allow control-C to stop tincd without stopping the tinc shell. + Don't forget the Port variable when creating an invitation URL. + Choose a different Port when 655 isn't available when doing "tinc init". + Choose a different Port when 655 isn't available when doing "tinc join". + Make absolutely sure we can write config files before accepting an invitation. + Defer handling netname conflicts when accepting an invitation. + Use umask() to set file and UNIX socket permissions without race conditions. + Clean up the SIGINT handler. + Really retry outgoing connections immediately if requested. + Non-zero exit code when reloading config file fails after SIGHUP. + Fix a typo. + Don't echo broadcast packets back when Broadcast = direct. + Move .h files from noinst_HEADERS to tincd_SOURCES. + Build .tar.gz instead of .tar.xz. + Update copyright notices. + Don't typedef the same struct in two header files. + Releasing 1.1pre8. + +Etienne Dechamps (5): Fix combination of Mode = router and DeviceType = tap on Linux. + Fix hash_function(). + Disable PMTU discovery when TCPOnly is set. + Introduce lightweight PMTU probe replies. + Further improve bandwidth estimation for type 2 MTU probe replies. -Version 1.0.21 April 22 2013 +Sven-Haegar Koch (1): + Modified some error messages in src/sptps.c. + +Version 1.1pre7 April 22 2013 ------------------------------------------------------------------------ -Guus Sliepen (2): +Guus Sliepen (12): + Use UDP when using sptps_test in datagram mode. + Flush output buffers in the tap reader thread on Windows. + Better default output file for generated public keys. + Allow changing configuration with tincctl without the "config" keyword. + Avoid calling time(NULL). + Include README.android in the tarballs. + Rename tincctl to tinc. + Remove references to the config keyword. + Describe the SPTPS protocol in the manual. + Fix completion of add/del/get/set commands. Drop packets forwarded via TCP if they are too big (CVE-2013-1428). - Releasing 1.0.21. + Releasing 1.1pre7. -Version 1.0.20 March 03 2013 +Version 1.1pre6 February 20 2013 ------------------------------------------------------------------------ -Guus Sliepen (30): - Use /dev/tap0 by default on FreeBSD and NetBSD when using Mode = switch. - Document how to load the tap driver on FreeBSD. - Update THANKS file. - Also clarify hostnames=[yes|no] in tinc.conf(5). - Attribution for Vil Brekin and some code style cleanups. - Don't ignore Makefile.am. - Fix links in documenation. - Attribution for Martin Schürrer. - Add strict checks to hex to binary conversions. - Clear connection options and status fields in free_connection_partially(). - Fix warnings from cppcheck. - Clear Ethernet header when reading packets from a tun device. - Clear status and options fields of unreachable nodes. +Guus Sliepen (16): + Fix datagram SPTPS. + Fix a typo. + Get microsecond time resolution on Windows. + Detect increases in PMTU. + Remove direct inclusion of OpenSSL headers in net_packet.c and tincd.c. + Fix tincd terminating immediately on Windows. + Check for writability when waiting for a socket to finish connecting. + Fix segmentation fault when trying to connect via a SOCKS5 proxy. + Don't send proxy requests for incoming connections. + Derive UNIX socket filename from PID filename. + Let the GUI use UNIX sockets if available. + Don't expect a response from tincd after sending REQ_STOP. + Fix a tiny memory leak. + Fix compiler warnings on Windows. + Fix compiler warnings on some BSD variants. + Releasing 1.1pre6. + +Version 1.1pre5 January 20 2013 +------------------------------------------------------------------------ + +Guus Sliepen (24): + Clarify the description of IndirectData and Mode = router. + Fix display of cumulative packet counters. + Fix infinite loop in timeout handling on Windows. + Fix support for tunemu on iOS devices. + Fix a typo. + Note that node Names are case sensitive. + Note that tincctl import is only meant to work with data from tincctl export. + Mention that the -L, -R and -U options are not supported on all platforms. + Don't complain about garbage if we skipped importing a host file. + Better error messages when using -L, -R or -U on platforms that do not support it. + Always complain if too many arguments are given for tincctl commands. + Check HMAC before sequence number. + Add the tincctl exchange and exchange-all commands. + Count the number of correctly received UDP packets. + Estimate RTT, bandwidth and packet loss between nodes. + Fix the minimum spanning tree algorithm. + Handle SIGINT gracefully. + Move make_names() and related variables to its own source file. + Fix compilation of UML and VDE device support. + Allow connections via UNIX sockets. + Make sure PriorityInheritance also works in switch mode. + Remove possible definition of timersub(), which is also in dropin.h. + Fix tincctl init when /etc/tinc does not yet exist. + Releasing 1.1pre5. + +Version 1.1pre4 December 05 2012 +------------------------------------------------------------------------ + +Guus Sliepen (35): Fix warnings from groff. + Keep track of the number of nodes in a tree. + Add the AutoConnect option. + Slightly randomize all timeouts. + Fix potential buffer overflow reading the PID file. Using alloca() for a constant sized buffer is very silly. Make sure PMTU discovery works in switch mode with VLAN tags. + Mention libcurses and libreadline in the manual. Mention in the manual that support for LZO and zlib can be disabled. + Fix index entry for section about readline library. Fix configure script help text for --enable options. Don't take the address of a variable whose scope is about to disappear. Send broadcast packets using a random socket, and properly support IPv6. Remove text saying you must have one of PrivateKey or PrivateKeyFile in tinc.conf. - Fix support for tunemu on iOS devices. - Make sure PriorityInheritance also works in switch mode. - Detect increases in PMTU. - Fix a compiler warning. - Fix segmentation fault when trying to connect via a SOCKS5 proxy. - Don't send proxy requests for incoming connections. - Fix compiler warnings on Windows. - Fix detection of rejected SOCKS5 proxy requests. - Releasing 1.0.20. + Disable support for kqueue on MacOS/X. + Also don't use poll() on MacOS/X. + Choose a suitable socket when updating a node's UDP address. + Try all known addresses of node during PMTU discovery, now also for SPTPS. + Improve UDP address selection. + Ensure MTU probe replies are sent back the same way they came in. + Drop libevent and use our own event handling again. + Allow multiple timeouts to expire at the exact same time. + Fix check for expired events. + Fix use of unitialised values in hash tables. + Set a node's pointers to zero before trying to insert it into a tree. + Fix crash in timeout handling. + Fix compiler error on Windows. + More fixes for Windows. + Add option to dump only a list of reachable nodes. + Remove GraphDumpFile from the manual and manpages. + Fix compiler warnings on OpenBSD. + Don't use nested functions. + Scale packet counters similar to byte counters. + Fix whitespace. + Releasing 1.1pre4. + +Version 1.1pre3 October 14 2012 +------------------------------------------------------------------------ + +Guus Sliepen (384): + Created the 1.1 branch where large code changes can take place, + Only free members of connection_t that have been allocated. + Port fixes from release 1.0.8. + Properly delete listener socket events on shutdown. + 128 listener sockets is way too much. + Use a separate event structure to handle meta data writes. + Use libevent to dump graphs when necessary. + Use libevent to handle HUP signal. + Configure events after obtaining a socket. + Use libevent to send MTU probes. + Use libevent for retrying outgoing connections. + Remove legacy event system. + Properly use the timeout_initialized() macro. + Use libevent to handle all non-fatal signals. + Redo SIGALRM handling. + Use libevent to age past requests. + Use libevent to age learned MAC addresses. + Use libevent to handle key expiration. + Move key regeneration handling to net_setup.c. + Remove global variable "now". + Remove the last bits of the legacy main_loop(). + Remove last references to the global variable "running". + K&R style braces + Use splay trees instead of AVL trees. + Detect duplicate outgoing connections. + More consistent variable naming. + Show branch version number. + Update documentation. + Start of control socket implementation. + We can safely delete a connection_t in terminate_connection() now. + Fix retrying outgoing connections. + Remove pidfile in favour of control socket. + Move key generation to tincctl. + Implement "stop" command, and allow tincctl to retrieve a running tincd's PID. + Use bufferevents to handle control socket buffering. + Use libevent for meta socket input/output buffering. + Parse PEM RSA keys ourself, and use libgcrypt to do RSA encryption and decryption. + Create wrappers for the cryptographic operations used in tinc. + Make sure the crypto wrapper functions can actually be compiled. + Some more crypto wrapper functions are needed. + Finish crypto wrapping. Also provide wrappers for OpenSSL. + Only check for libgcrypt if --with-gcrypt is used. + Fix formatting of --help output. + Small fixes to make gcrypt routines compile. + Apply patch from Scott Lamb: Update documentation to match tincctl changes + Fix connection weight estimation. + Use a dummy function as the read callback for connection bufferevents. Should not be triggered. + Fix meta data segfault when receiving a partial command. + Prevent double free() of a used challenge nonce. + Look in the configured sbin directory for the tincd binary. + Only show meta connection related debug messages when debug level >= 4 + Move AC_GNU_SOURCE up to make autoconf happy. + Use the crypto wrappers again instead of calling OpenSSL directly. + Backport fixes from trunk since revision 1555. + Fix compiler warnings. + Remove unnecessary parentheses from sizeof, apply sizeof to variables instead of types whereever possible. + Remove wrong checks. + Use Dijkstra's algorithm. Based on patches from Max Rijevskiy. + Make sure IPv6 sockets are IPv6 only. + Move RSA key generation into the wrappers. + Merge branch 'master' into 1.1 + Merge branch 'master' into 1.1 + Handle truncated message authentication codes. + Fix pointer arithmetic when creating and verifying message authentication codes. + Merge branch 'master' into 1.1 + Add missing #include. + Use correct format specifiers. + Replace asprintf()s not covered by the merge to xasprintf(). + Add a better autoconf check for libevent. + Merge branch 'master' into 1.1 + Drop localisation and checkpoint tracing in files not covered by the merge. + Update FSF address in files not covered by the merge. + Merge branch 'master' into 1.1 + Don't enable device events when there is no valid filedescriptor. + Use %x instead of %lx where appropriate. + Handle truncated message authentication codes with gcrypt. + Handle PKCS#5 padding in the gcrypt backend. + Make sure the 1.1 branch compiles in a MinGW environment. + Better integration of libevent in build system. + Small fixes to get really working control sockets on Windows. + Use the TCP socket infrastructure for control sockets. + Only call ioctlsocket() on Windows. + Merge branch 'master' into 1.1 + Fix compiler warnings. + Do not include OpenSSL headers directly. + Include missing header files and source directories. + Allow connections to be closed. + Start of a GUI for tinc. + Fix packet authentication. + Fix block cipher padding when using libgcrypt. + Reinitialise block cipher IV each time we encrypt a packet when using libgcrypt. + Fix reading raw RSA keys with libgcrypt. + recv() and recvfrom() return int, do not prematurely cast the return value. + Do not consider unreachable nodes when trying to determine packet origin. + Fix alignment of results of RSA operations when using libgcrypt. + Do not use hardcoded cipher block length when padding. + Remove unused AVL tree library. + Move source from lib/ to src/. + Fix experimental GUI when reading hexadecimal values. + Merge branch 'master' into 1.1 + Fix merge of commit 4a0b9981513059755b9fd15b38fc198f46a0d6f2. + Add missing return statement. + Use correct digest length when checking a received key. + Do not try to free NULL pointers. + Remove obsolete lib/ directory. + Merge branch 'master' into 1.1 + Link tincctl with dropin.o. + Merge branch 'master' into 1.1 + Do not try to dereference myself->connection->config_tree. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Fix check for event initialization due to the merge. + Add simple buffer management code. + Remove use of bufferevent and eventbuffers, use our own buffering instead. + Several fixes for the buffer code. + Add per-node traffic counters. + Dump traffic statistics over control sockets. + Add an autoconf check for the curses library. + Add a very primitive "top" command to tincctl. + Allow inserting items in the middle of a list. + Nicer top command. + Add tincctl.h. + Add top.h. + Use GetItemCount() on ListCtrls instead of directly accessing ItemCount. + Fix some compiler warnings. + Compact input buffer before trying to read instead of after. + Always compact the buffer if it has reached MAXBUFSIZE. + Check if an event is initialized before calling event_del(). + Reset tcplen after use. + Add the ability to dump all traffic going through route() over a control connection. + Allow tincctl to connect to something besides localhost. + Show hostname and port in error message when connecting to a running tincd. + Cosmetic fix when pressing 's' in tincctl top. + Initialise priority field to zero for packets read from the VPN interface. + Remove outgoing event in free_connection(). + Simplify signal handling. + Drop the GNU malloc.c, realloc.c, and xmalloc.c. + Drop the GNU memcmp.c implementation. + Don't #include anymore. + Remove unused functions and variables. + Remove support for the Ethertap device. + Fix some compiler and cppcheck warnings. + More stable sorting in tincctl top. + Make traffic statistics more readable with configurable scaling. + Fix nodes joining the VPN after tincctl top started. + Don't treat packets coming in via TCP as having zero length. + Remove debugging message that was accidentily left in. + Even simpler signal handling. + Small fixes for Windows. + Use send() when writing to sockets, and the return type is ssize_t. + Fix format strings for Windows. + Don't ignore SIGCHLD, system() needs it. + Clean up digests when freeing a connection_t. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Reopen log file after SIGHUP. + Only log UDP address changes at the appropriate debug levels. + No need to check for pselect() in tinc 1.1. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Delete mtuevent if it is not used. + Don't call event_del() from the mtuevent handler, always send_mtu_probe() in ans_key_h(). + Don't use AM_CONDITIONAL for CURSES. + Add Makefile.am in gui/. + Update manpages and info manual. + Ensure that the texinfo manual can be converted to HTML. + Releasing 1.1pre1. + Ensure the right files end up in the tarball after make dist. + Thank Scott Lamb, Sven-Haegar Koch and Loïc Grenié in the NEWS file. + Merge Tinc.py into tinc-gui to simplify make install. + Re-add support for SIGALRM. + Don't call exit_control() if we didn't do init_control(). + Rename controlcookie file to pidfile. + Make pid files backwards compatible and add address of listening socket. + Add +git to the version string. + Really stable sorting of tincctl top output. + Use pidfile in tinc-gui as well. + Don't react to escape character in tincctl top. + Update documentation to mention pidfiles instead of controlcookies. + Remove debug messages that were printed to stdout. + Add manpage for tinc-gui. + Preliminary implementation of Elliptic Curve Diffie-Hellman Ephemeral key exchange. + Support ECDH key exchange. + Add PRF to derive key material from the ECDH shared secret. + Use PRF. + Proper use of PRF. + No need to keep around pointers to EC_GROUP. + Cleanups in ECDH code. + Base64 encoding and decoding functions. + Add ECDSA key generation. + Have tincctl generate ECDSA keys. + Finish base64 decoding routine. + Add ECDSA key import. + Round up the size of the secret parts after splitting it in two. + Add a minor number to the protocol version. + Bump minor protocol to indicate ECDH capability for UDP session keys. + Implement ECDSA sign and verify operations. + Read ECDSA keys. + Very primitive ECDSA signed ECDH key exchange for the meta protocol. + Hash input before signing it with ECDSA. + Free ECDSA and RSA structures when freeing a connection_t. + Automatically exchange ECDSA keys and upgrade to new authentication protocol. + Close meta connection socket after cleaning up event structures. + Require ExperimentalProtocol = yes for new features, update documentation. + Don't use wildcards in filenames in configure.in. + Make hexadecimal and base64 routines behave the same. + Make use of the improved hex and base64 functions. + Remove unnecessary variables and functions. + Fix compiler warnings. + Use the correct direction flag when setting cipher keys. + Use the same logic as tinc 1.0.x for detecting two nodes with the same Name. + Use ECDSA to sign ECDH key exchange for UDP session keys. + Update info manual. + Use usleep() instead of sleep(), MinGW complained. + Use const pointer to source in base64 and hex routines. + Ensure symlinked files do not end up in the tarball. + Fix declaration of usleep(). + "tincctl stop" now removes the tinc service on Windows. + Write loopback address instead of "any" address in pidfile. + Add missing newline. + Releasing 1.1pre2. + Fix tinc 1.0.x daemons connecting when ExperimentalProtocol = yes. + Don't abort() on low-level crypto errors, just return false. + Start of "Simple Peer-To-Peer Security" protocol. + Handle UDP packets with unknown source addresses properly. + Fix compiler warning. + Update SPTPS protocol. + Test corner cases in the SPTPS protocol. + Add counter mode encryption. + Use counter mode encryption. + Exchange ACK records to indicate switch to new keys. + Fix compiler warnings. + Fix a few small memory leaks. + Use only one hash algorithm (SHA512) in the PRF. + Remove useless warning about signature length being shorter than expected. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Apply HMAC after encryption. + Use SPTPS when ExperimentalProtocol is enabled. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Go back to breadth first search for path finding. + Ensure all SPTPS functions are prefixed with sptps_. + Let tincctl use the NETNAME environment variable if no -n option is given. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Don't close control connections when handling a reload command. + Allow log messages to be captured by tincctl. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Allow CTR mode counter to be set to a specific value. + Add datagram mode to the SPTPS protocol. + Test SPTPS messages sent while key renegotation is in progress. + Don't send an ACK message after the first key exchange in the SPTPS protocol. + Start documenting the SPTPS protocol. + Make sure the signature also covers the session label. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Add autoconf checks for OpenSSL's elliptic curve functions. + Update README to reflect that only OpenSSL is currently supported. + Always pass request strings to other functions as const char *. + Don't forget to send a newline when forwarding requests. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Fix crash when handling the ALRM signal. + Use /dev/tap0 by default on FreeBSD and NetBSD when using Mode = switch. + Document how to load the tap driver on FreeBSD. + Update THANKS file. + Merge branch 'master' into 1.1 + "tincctl init" creates initial directory structure, tinc.conf and keypairs. + Put every command in its own function. + Allow configuration variables to be added/removed using tincctl. + Stricter checks for node names. + Add an easy way to edit a configuration file. + Have tincctl notify a running tincd of configuration file changes. + Fix tincctl start. + Let tincctl ignore tincd options, so they will be passed on. + Fix tincctl dump. + Move all functions related to subnet parsing to subnet_parse.c. + "tincctl info" gives more human readable information about nodes or subnets. + Give an error message when tincctl info cannot parse the given subnet or address. + Strip default subnet weight from output. + Add an easy way to export and import host configuration files. + When exporting configuration files, don't copy Name variables. + Put minor protocol version in connection options so other nodes can see it. + Use minor protocol version to determine whether to use ECDH key exchange between nodes. + Never remove items from cmdline_conf. + Split setup_myself() into two functions, one for reloading configuration. + Allow more configuration variables to be changed when reloading configuration. + Prefer routes with lower weight as long as they do not increase the number of hops. + Make sure tinc compiles on Windows. + Make sure sptps.h and info.h are in the tarball. + BSD make doesn't like $<. + Fix various compiler warnings. + Call event_init() after detaching. + Add some checks when changing configuration. + Add a newline to a configuration file if it is missing. + Have tincd and tincctl use the same method of determining netname. + Fix some compiler warnings. + Fix crash when no netname is specified. + Don't try to mkdir(CONFDIR) on Windows when there is a registry key for tinc. + Use backslashes on Windows. + Windows doesn't like quotes around "edit" when calling it through system(). + Fix exit code when installing tincd as a service on Windows. + tincctl init now also creates a template tinc-up script. + Have tinc-gui use same way of locating pidfile as tincd and tincctl. + Remove unused po/ directory. + Also clarify hostnames=[yes|no] in tinc.conf(5). + Merge branch 'master' into 1.1 + Use datagram SPTPS for packet exchange between nodes. + Remove unused #include. + Handle SPTPS datagrams in try_mac(). + Add Brandon Black's replay window code to SPTPS. + Use a status bit to track which nodes use SPTPS. + Try sending SIGTERM if we cannot connect to a tincd but we know its PID. + tincctl restart should work even if no tincd is running. + Add the ability to query configuration variables to tincctl. + Add missing configuration variables. + Stricter checks for netname and node names. + Update the documentation to encourage using "tincctl init" and "tincctl config". + Clear struct sptps before reusing it. + Have tincctl act as a shell when no command is given. + Optionally compress and/or strip Ethernet header from SPTPS packets. + Add readline completion for tincctl config and tincctl info. + Fork when using the "start" command in tincctl. + Make sure the top command can be used more than once in tincctl's shell. + Add bash completion script. + Fix segfault when using tincctl's shell without readline. + Quit when "exit" or "quit" commands are used in tincctl's shell. + Fix node name check for "connect" and "disconnect" commands. + Properly handle SPTPS packets with stripped Ethernet headers. + Remove some debug messages. + Remove newlines at end of log messages. + Add a simple hash table implementation. + Use hash tables to lookup owners of addresses. + Replace node_udp_tree with a hash table. + Ensure sptps_test compiles with -flto. + Attribution for Vil Brekin and some code style cleanups. + Don't ignore Makefile.am. + Fix typo in manpage. + Remove remnants of Ethertap and old TUNSETIFF ioctl(). + Keep last known address and time since reachability changed. + Let tincctl parse and format dumps. + Allow dumping either directed or undirected graphs. + Update documentation of the "dump graph" command. + Comment out old public/private keys when generating new ones. + Fix links in documentation. + Fix links in documenation. + Let the GUI handle the new dump format. + Fix column sorting, make all lists sortable. + Correctly add/remove outgoing connections when reloading configuration. + Make tincctl robust against dropped control connections. + Remove some debugging messages. + Attribution for Martin Schürrer. + Add strict checks to hex to binary conversions. + Merge branch 'master' into 1.1 + Fix not reading Port statement from host config file. + Remove unused function declaration. + Make sure sptps_test compiles without -flto. + Remove abort() call that accidentily sneaked into commit dd1b69e. + Libreadline might depend on libcurses. + Fix off-by-one error. + Improve starting/stopping tincd using tincctl. + Clear connection options and status fields in free_connection_partially(). + When terminating, keep control connections open until the end. + Useful error messages when writing to a meta connection fails. + Make datagram SPTPS key exchange more robust. + Handle packets encrypted via SPTPS that need to be forwarded via TCP. + Remove a debug message. + Fix warnings from cppcheck. + Refactor outgoing connection handling. + Replace the connection_tree with a connection_list. + C99 extravaganza. + Fix deleting connections from the connection list. + Remove unused variables, fix some #includes. + Clear Ethernet header when reading packets from a tun device. + Fix memory leaks found by valgrind. + Fix hash functions for keys whose size is not divisible by 4. + Try all known addresses of node during the PMTU discovery phase. + Fix whitespace. + Clear status and options fields of unreachable nodes. + Strip newline from incoming SPTPS requests. + Fix handling of initial datagram SPTPS packet. + Only log success of initial datagram SPTPS handshake. + Make sure the ReplayWindow option works for SPTPS as well. + Log more messages using logger(). + tincctl: add node colors and edge weight to graph dump. + Fix compile error on Windows. + Update copyright notices. + Fix a few compiler errors/warnings. + Releasing 1.1pre3. + +Sven-Haegar Koch (29): + Merge branch 'master' into 1.1 + Fixed 1.0 miss-merges + Add missing AC_CHECK_HEADERS([dirent.h]) to configure.in + Function flush_meta() does not exist anymore. + README.git: tinc 1.1 needs libevent + Demote all LOG_EMERG to LOG_ERR, spamming all xterms is bad. + Fixed metadata protokoll corruption on forwarded requests + Fixed error logging on "Input buffer full" condition. + Removed two newlines from the end of log messages which created empty lines. + Use same definition for xalloc_fail_func as is really used. + sparse fixup: error: dubious one-bit signed bitfield + sparse fixup: error: too many arguments for function send_key_changed + sparse fixup: warning: symbol '...' was not declared. Should it be static? + sparse fixup: warning: non-ANSI function declaration of function '...' + sparse fixup: warning: Using plain integer as NULL pointer + fgets() returns NULL on error, not < 0 + src/net_socket.c bind_to_address(): Use after free in error path. + do_outgoing_connection() may delete a failed connection, and the structure + sptps_stop(): clear pointers after free to avoid double free. + Remove confusing error message for failed reading in ECDSA keys. + ecdh & ecdsa: avoid some possible memory leaks in error conditions. + terminate_connection(): Avoid use-after-free and double-free for + terminate_connection(): only kill c->node->connection if it is pointing + free_connection_partially(): Avoid possible use-after-free for c->hischallenge + Label control connections for log output as "", not "". + terminate_connection(): delete non-outgoing (aka incoming) connections. + Silence SPTPS log messages, reduce them from DEBUG_ALWAYS to DEBUG_META. + free_connection_partially(): also reset remote protocol version infos + sptps.c: Add missing newline to log message. + +Scott Lamb (19): + Rename "event_t" to "tevent_t", along with associated functions. + A couple missed tevent things. + Convert to libevent. + Lots of svn:ignore entries + Revert to only requiring autoconf 2.59. + Refresh po/POTFILES.in. + Updated svn:ignores list for new symlinked sources and tincctl. + const correctness + Temporarily revert to old crypto code + Update documentation to match tincctl changes + Fix reload crash + Fancier protocol for control socket + Dump through control socket + Purge through the control socket + Alter debugging levels through control socket + Retry connections through control socket + Reload configuration through control socket + Coding style corrections + Use a control socket directory to restrict access Vilbrekin (5): Basic patch for android cross-compilation. @@ -424,6 +1531,11 @@ Vilbrekin (5): Use __ANDROID__ define rather than dirty hard-code to allow android NDK cross-compilation. Android cross-compilation instructions. +Michael Tokarev (3): + don't mention reload twice in tincctl help + run tincd from the same directory as tincctl and pass all options to it + use execvp() not execve() in tincctl start + Martin Schürrer (1): Output details of encryption errors @@ -522,6 +1634,275 @@ Guus Sliepen (4): Use usleep() instead of sleep(), MinGW complained. Releasing 1.0.16. +Version 1.1pre2 July 17 2011 +------------------------------------------------------------------------ + +Guus Sliepen (54): + Ensure the right files end up in the tarball after make dist. + Thank Scott Lamb, Sven-Haegar Koch and Loïc Grenié in the NEWS file. + Merge Tinc.py into tinc-gui to simplify make install. + Re-add support for SIGALRM. + Don't call exit_control() if we didn't do init_control(). + Rename controlcookie file to pidfile. + Make pid files backwards compatible and add address of listening socket. + Add +git to the version string. + Really stable sorting of tincctl top output. + Use pidfile in tinc-gui as well. + Don't react to escape character in tincctl top. + Update documentation to mention pidfiles instead of controlcookies. + Remove debug messages that were printed to stdout. + Add manpage for tinc-gui. + Preliminary implementation of Elliptic Curve Diffie-Hellman Ephemeral key exchange. + Support ECDH key exchange. + Add PRF to derive key material from the ECDH shared secret. + Use PRF. + Proper use of PRF. + No need to keep around pointers to EC_GROUP. + Cleanups in ECDH code. + Base64 encoding and decoding functions. + Add ECDSA key generation. + Have tincctl generate ECDSA keys. + Finish base64 decoding routine. + Add ECDSA key import. + Round up the size of the secret parts after splitting it in two. + Add a minor number to the protocol version. + Bump minor protocol to indicate ECDH capability for UDP session keys. + Implement ECDSA sign and verify operations. + Read ECDSA keys. + Very primitive ECDSA signed ECDH key exchange for the meta protocol. + Hash input before signing it with ECDSA. + Free ECDSA and RSA structures when freeing a connection_t. + Automatically exchange ECDSA keys and upgrade to new authentication protocol. + Close meta connection socket after cleaning up event structures. + Require ExperimentalProtocol = yes for new features, update documentation. + Don't use wildcards in filenames in configure.in. + Make hexadecimal and base64 routines behave the same. + Make use of the improved hex and base64 functions. + Remove unnecessary variables and functions. + Fix compiler warnings. + Use the correct direction flag when setting cipher keys. + Use the same logic as tinc 1.0.x for detecting two nodes with the same Name. + Use ECDSA to sign ECDH key exchange for UDP session keys. + Update info manual. + Use usleep() instead of sleep(), MinGW complained. + Use const pointer to source in base64 and hex routines. + Ensure symlinked files do not end up in the tarball. + Fix declaration of usleep(). + "tincctl stop" now removes the tinc service on Windows. + Write loopback address instead of "any" address in pidfile. + Add missing newline. + Releasing 1.1pre2. + +Version 1.1pre1 June 25 2011 +------------------------------------------------------------------------ + +Guus Sliepen (164): + Created the 1.1 branch where large code changes can take place, + Only free members of connection_t that have been allocated. + Port fixes from release 1.0.8. + Properly delete listener socket events on shutdown. + 128 listener sockets is way too much. + Use a separate event structure to handle meta data writes. + Use libevent to dump graphs when necessary. + Use libevent to handle HUP signal. + Configure events after obtaining a socket. + Use libevent to send MTU probes. + Use libevent for retrying outgoing connections. + Remove legacy event system. + Properly use the timeout_initialized() macro. + Use libevent to handle all non-fatal signals. + Redo SIGALRM handling. + Use libevent to age past requests. + Use libevent to age learned MAC addresses. + Use libevent to handle key expiration. + Move key regeneration handling to net_setup.c. + Remove global variable "now". + Remove the last bits of the legacy main_loop(). + Remove last references to the global variable "running". + K&R style braces + Use splay trees instead of AVL trees. + Detect duplicate outgoing connections. + More consistent variable naming. + Show branch version number. + Update documentation. + Start of control socket implementation. + We can safely delete a connection_t in terminate_connection() now. + Fix retrying outgoing connections. + Remove pidfile in favour of control socket. + Move key generation to tincctl. + Implement "stop" command, and allow tincctl to retrieve a running tincd's PID. + Use bufferevents to handle control socket buffering. + Use libevent for meta socket input/output buffering. + Parse PEM RSA keys ourself, and use libgcrypt to do RSA encryption and decryption. + Create wrappers for the cryptographic operations used in tinc. + Make sure the crypto wrapper functions can actually be compiled. + Some more crypto wrapper functions are needed. + Finish crypto wrapping. Also provide wrappers for OpenSSL. + Only check for libgcrypt if --with-gcrypt is used. + Fix formatting of --help output. + Small fixes to make gcrypt routines compile. + Apply patch from Scott Lamb: Update documentation to match tincctl changes + Fix connection weight estimation. + Use a dummy function as the read callback for connection bufferevents. Should not be triggered. + Fix meta data segfault when receiving a partial command. + Prevent double free() of a used challenge nonce. + Look in the configured sbin directory for the tincd binary. + Only show meta connection related debug messages when debug level >= 4 + Move AC_GNU_SOURCE up to make autoconf happy. + Use the crypto wrappers again instead of calling OpenSSL directly. + Backport fixes from trunk since revision 1555. + Fix compiler warnings. + Remove unnecessary parentheses from sizeof, apply sizeof to variables instead of types whereever possible. + Remove wrong checks. + Use Dijkstra's algorithm. Based on patches from Max Rijevskiy. + Make sure IPv6 sockets are IPv6 only. + Move RSA key generation into the wrappers. + Merge branch 'master' into 1.1 + Merge branch 'master' into 1.1 + Handle truncated message authentication codes. + Fix pointer arithmetic when creating and verifying message authentication codes. + Merge branch 'master' into 1.1 + Add missing #include. + Use correct format specifiers. + Replace asprintf()s not covered by the merge to xasprintf(). + Add a better autoconf check for libevent. + Merge branch 'master' into 1.1 + Drop localisation and checkpoint tracing in files not covered by the merge. + Update FSF address in files not covered by the merge. + Merge branch 'master' into 1.1 + Don't enable device events when there is no valid filedescriptor. + Use %x instead of %lx where appropriate. + Handle truncated message authentication codes with gcrypt. + Handle PKCS#5 padding in the gcrypt backend. + Make sure the 1.1 branch compiles in a MinGW environment. + Better integration of libevent in build system. + Small fixes to get really working control sockets on Windows. + Use the TCP socket infrastructure for control sockets. + Only call ioctlsocket() on Windows. + Merge branch 'master' into 1.1 + Fix compiler warnings. + Do not include OpenSSL headers directly. + Include missing header files and source directories. + Allow connections to be closed. + Start of a GUI for tinc. + Fix packet authentication. + Fix block cipher padding when using libgcrypt. + Reinitialise block cipher IV each time we encrypt a packet when using libgcrypt. + Fix reading raw RSA keys with libgcrypt. + recv() and recvfrom() return int, do not prematurely cast the return value. + Do not consider unreachable nodes when trying to determine packet origin. + Fix alignment of results of RSA operations when using libgcrypt. + Do not use hardcoded cipher block length when padding. + Remove unused AVL tree library. + Move source from lib/ to src/. + Fix experimental GUI when reading hexadecimal values. + Merge branch 'master' into 1.1 + Fix merge of commit 4a0b9981513059755b9fd15b38fc198f46a0d6f2. + Add missing return statement. + Use correct digest length when checking a received key. + Do not try to free NULL pointers. + Remove obsolete lib/ directory. + Merge branch 'master' into 1.1 + Link tincctl with dropin.o. + Merge branch 'master' into 1.1 + Do not try to dereference myself->connection->config_tree. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Fix check for event initialization due to the merge. + Add simple buffer management code. + Remove use of bufferevent and eventbuffers, use our own buffering instead. + Several fixes for the buffer code. + Add per-node traffic counters. + Dump traffic statistics over control sockets. + Add an autoconf check for the curses library. + Add a very primitive "top" command to tincctl. + Allow inserting items in the middle of a list. + Nicer top command. + Add tincctl.h. + Add top.h. + Use GetItemCount() on ListCtrls instead of directly accessing ItemCount. + Fix some compiler warnings. + Compact input buffer before trying to read instead of after. + Always compact the buffer if it has reached MAXBUFSIZE. + Check if an event is initialized before calling event_del(). + Reset tcplen after use. + Add the ability to dump all traffic going through route() over a control connection. + Allow tincctl to connect to something besides localhost. + Show hostname and port in error message when connecting to a running tincd. + Cosmetic fix when pressing 's' in tincctl top. + Initialise priority field to zero for packets read from the VPN interface. + Remove outgoing event in free_connection(). + Simplify signal handling. + Drop the GNU malloc.c, realloc.c, and xmalloc.c. + Drop the GNU memcmp.c implementation. + Don't #include anymore. + Remove unused functions and variables. + Remove support for the Ethertap device. + Fix some compiler and cppcheck warnings. + More stable sorting in tincctl top. + Make traffic statistics more readable with configurable scaling. + Fix nodes joining the VPN after tincctl top started. + Don't treat packets coming in via TCP as having zero length. + Remove debugging message that was accidentily left in. + Even simpler signal handling. + Small fixes for Windows. + Use send() when writing to sockets, and the return type is ssize_t. + Fix format strings for Windows. + Don't ignore SIGCHLD, system() needs it. + Clean up digests when freeing a connection_t. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Reopen log file after SIGHUP. + Only log UDP address changes at the appropriate debug levels. + No need to check for pselect() in tinc 1.1. + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + Delete mtuevent if it is not used. + Don't call event_del() from the mtuevent handler, always send_mtu_probe() in ans_key_h(). + Don't use AM_CONDITIONAL for CURSES. + Add Makefile.am in gui/. + Update manpages and info manual. + Ensure that the texinfo manual can be converted to HTML. + Releasing 1.1pre1. + +Scott Lamb (19): + Rename "event_t" to "tevent_t", along with associated functions. + A couple missed tevent things. + Convert to libevent. + Lots of svn:ignore entries + Revert to only requiring autoconf 2.59. + Refresh po/POTFILES.in. + Updated svn:ignores list for new symlinked sources and tincctl. + const correctness + Temporarily revert to old crypto code + Update documentation to match tincctl changes + Fix reload crash + Fancier protocol for control socket + Dump through control socket + Purge through the control socket + Alter debugging levels through control socket + Retry connections through control socket + Reload configuration through control socket + Coding style corrections + Use a control socket directory to restrict access + +Sven-Haegar Koch (18): + Merge branch 'master' into 1.1 + Fixed 1.0 miss-merges + Add missing AC_CHECK_HEADERS([dirent.h]) to configure.in + Function flush_meta() does not exist anymore. + README.git: tinc 1.1 needs libevent + Demote all LOG_EMERG to LOG_ERR, spamming all xterms is bad. + Fixed metadata protokoll corruption on forwarded requests + Fixed error logging on "Input buffer full" condition. + Removed two newlines from the end of log messages which created empty lines. + Use same definition for xalloc_fail_func as is really used. + sparse fixup: error: dubious one-bit signed bitfield + sparse fixup: error: too many arguments for function send_key_changed + sparse fixup: warning: symbol '...' was not declared. Should it be static? + sparse fixup: warning: non-ANSI function declaration of function '...' + sparse fixup: warning: Using plain integer as NULL pointer + fgets() returns NULL on error, not < 0 + src/net_socket.c bind_to_address(): Use after free in error path. + do_outgoing_connection() may delete a failed connection, and the structure + Version 1.0.15 June 24 2011 ------------------------------------------------------------------------ diff --git a/Makefile.am b/Makefile.am index 8e43fe5..1237140 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,14 +2,39 @@ AUTOMAKE_OPTIONS = gnu -SUBDIRS = src doc systemd +SUBDIRS = src doc test systemd bash_completion.d -ACLOCAL_AMFLAGS = -I m4 +ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = COPYING.README README.android +@CODE_COVERAGE_RULES@ + +# If git describe works, force autoconf to run in order to make sure we have the +# current version number from git in the resulting configure script. +configure-version: + -cd $(srcdir) && git describe && autoconf --force + +# Triggering the README target means we are building a distribution (make dist). +README: configure-version + ChangeLog: - git log > ChangeLog + (cd $(srcdir) && git log) > ChangeLog + +deb: + dpkg-buildpackage -rfakeroot + +rpm: dist + cp $(distdir).tar.gz /usr/src/redhat/SOURCES/ + cp redhat/tinc.spec /usr/src/redhat/SOURCES/ + cd /usr/src/redhat/SOURCES/ && rpm -bb tinc.spec + +release: + rm -f ChangeLog + $(MAKE) ChangeLog + echo "Please edit the NEWS file now..." + /usr/bin/editor $(srcdir)/NEWS + $(MAKE) dist astyle: astyle --options=.astylerc -nQ src/*.[ch] src/*/*.[ch] diff --git a/Makefile.in b/Makefile.in index 2940df5..59f5738 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.2 from Makefile.am. +# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -94,9 +94,12 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ $(top_srcdir)/m4/ax_check_compile_flag.m4 \ $(top_srcdir)/m4/ax_check_link_flag.m4 \ - $(top_srcdir)/m4/ax_require_defined.m4 $(top_srcdir)/m4/lzo.m4 \ - $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/zlib.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ @@ -205,6 +208,8 @@ am__relativize = \ DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best DIST_TARGETS = dist-gzip +# Exists only to be overridden by the user if desired. +AM_DISTCHECK_DVI_TARGET = dvi distuninstallcheck_listfiles = find . -type f -print am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' @@ -219,8 +224,15 @@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ @@ -229,17 +241,21 @@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ MKDIR_P = @MKDIR_P@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ @@ -250,6 +266,8 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -307,8 +325,8 @@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AUTOMAKE_OPTIONS = gnu -SUBDIRS = src doc systemd -ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = src doc test systemd bash_completion.d +ACLOCAL_AMFLAGS = -I m4 EXTRA_DIST = COPYING.README README.android all: config.h $(MAKE) $(AM_MAKEFLAGS) all-recursive @@ -473,12 +491,6 @@ distdir: $(BUILT_SOURCES) $(MAKE) $(AM_MAKEFLAGS) distdir-am distdir-am: $(DISTFILES) - @case `sed 15q $(srcdir)/NEWS` in \ - *"$(VERSION)"*) : ;; \ - *) \ - echo "NEWS not updated; not releasing" 1>&2; \ - exit 1;; \ - esac $(am__remove_distdir) test -d "$(distdir)" || mkdir "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ @@ -621,7 +633,7 @@ distcheck: dist $(DISTCHECK_CONFIGURE_FLAGS) \ --srcdir=../.. --prefix="$$dc_install_base" \ && $(MAKE) $(AM_MAKEFLAGS) \ - && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) $(AM_DISTCHECK_DVI_TARGET) \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ @@ -797,8 +809,33 @@ uninstall-am: .PRECIOUS: Makefile +@CODE_COVERAGE_RULES@ + +# If git describe works, force autoconf to run in order to make sure we have the +# current version number from git in the resulting configure script. +configure-version: + -cd $(srcdir) && git describe && autoconf --force + +# Triggering the README target means we are building a distribution (make dist). +README: configure-version + ChangeLog: - git log > ChangeLog + (cd $(srcdir) && git log) > ChangeLog + +deb: + dpkg-buildpackage -rfakeroot + +rpm: dist + cp $(distdir).tar.gz /usr/src/redhat/SOURCES/ + cp redhat/tinc.spec /usr/src/redhat/SOURCES/ + cd /usr/src/redhat/SOURCES/ && rpm -bb tinc.spec + +release: + rm -f ChangeLog + $(MAKE) ChangeLog + echo "Please edit the NEWS file now..." + /usr/bin/editor $(srcdir)/NEWS + $(MAKE) dist astyle: astyle --options=.astylerc -nQ src/*.[ch] src/*/*.[ch] diff --git a/NEWS b/NEWS index c587e87..ffd6be2 100644 --- a/NEWS +++ b/NEWS @@ -1,758 +1,752 @@ -Version 1.0.36 August 26 2019 +# Version 1.1pre18 June 27 2021 - * Fix compiling tinc with certain versions of the OpenSSL library. - * Fix parsing some IPv6 addresses with :: in them. - * Fix GraphDumpFile output to handle node names starting with a digit. - * Fix a potential segmentation fault when fragmenting packets. +* Check all Address statements when making outgoing connections. +* Make more variables safe for use in invitations. +* Allow "tinc --force join" to accept all variables sent in an invitation. +* Make sure the stop command works on Windows if tincd is running in the + foreground. +* Handle DOS line endings in invitation files. +* Double-quote node names in dump graph output. +* Prevent large amounts of UDP probes being sent consecutively. +* Try harder to reconnect with unreachable nodes. +* Generate tinc-up.bat on Windows. +* Fix a possible infinite loop when adding Subnets to a running tincd. +* Allow a tun/tap filedescriptor to be passed through a UNIX socket. +* Use auto-clone tun/tap devices as default on FreeBSD and DragonFlyBSD. -Thanks to Rosen Penev, Quentin Rameau and Werner Schreiber for their +Thanks to Fabian Maurer, Ilia Pavlikhin, Maciej S. Szmigiero, Pacien +Tran-Girard, Aaron Li, Andreas Rammhold, Rosen Penev, Shengjing Zhu, Werner +Schreiber, iczero and leptonyu for their contributions to this version of tinc. + +# Version 1.1pre17 October 8 2018 + +* Prevent oracle attacks in the legacy protocol (CVE-2018-16737, + CVE-2018-16738). +* Prevent a MITM from forcing a NULL cipher for UDP in the legacy protocol + (CVE-2018-16758). +* AutoConnect is now enabled by default. +* Per-node network traffic statistics are now shown in the output of "info" and + "dump nodes" commands. + +Thanks to volth and Rafael Sadowski for their contributions to this version of +tinc. + +# Version 1.1pre16 June 12 2018 + +* Fixed building with support for UML sockets. +* Documentation updates and spelling fixes. +* Support for MSS clamping of IP-in-IP packets. +* Fixed parsing of the -b flag. +* Added the ability to set a firemall mark on sockets on Linux. +* Minor improvements to the build system. +* Added a cache of recently seen addresses of peers. +* Add support for --runstatedir to the configure script. +* Fixed linking with libncurses on some distributions. +* Automatically disable PMTUDiscovery when TCPOnly is enabled. +* Fixed removing the tinc service on Windows in some situations. +* Fixed the TAP-Win32 device locking up after waking up from suspend. + +Thanks to Todd C. Miller, Etienne Dechamps, Daniel Lublin, +Gjergji Ramku, Mike Sullivan and Oliver Freyermuth for their contributions to this version of tinc. -Version 1.0.35 October 5 2018 - - * Prevent oracle attacks (CVE-2018-16737, CVE-2018-16738). - * Prevent a MITM from forcing a NULL cipher for UDP (CVE-2018-16758). - * Minor fixes in the documentation. - -Thanks to Amine Amri and Rafael Sadowski for their contributions to this -version of tinc. - -Version 1.0.34 June 12 2018 - - * Fix a potential segmentation fault when connecting to an IPv6 peer via a - proxy. - * Minor improvements to the build system. - * Make the systemd service file identical to the one from the 1.1 branch. - * Fix a potential problem causing IPv4 sockets to not work on macOS. - -Thanks to Maximilian Stein and Wang Liu Shuai for their contributions to this -version of tinc. - -Version 1.0.33 November 4 2017 - - * Allow compilation from a build directory. - * Source code cleanups. - * Fix some options specified on the command line not surviving a HUP signal. - * Handle tun/tap device returning EPERM or EBUSY. - * Disable PMTUDiscovery when TCPOnly is used. - * Support the --runstatedir option of the autoconf 2.70. - -Thanks to Rafael Sadowski and Pierre-Olivier Mercier for their contributions to -this version of tinc. - -Version 1.0.32 September 2 2017 - - * Fix segmentation fault when using Cipher = none. - * Fix Proxy = exec. - * Support PriorityInheritance for IPv6 packets. - * Fixes for Solaris tun/tap support. - * Bind outgoing TCP sockets when ListenAddress is used. - -Thanks to Vittorio Gambaletta for his contribution to this version of tinc. - -Version 1.0.31 January 15 2017 - - * Remove ExecStop in tinc@.service. - -Thanks to Élie Bouttier for his contribution to this version of tinc. - -Version 1.0.30 October 30 2016 - - * Fix troubles connecting to some HTTP proxies. - - * Add mitigations for the Sweet32 attack when using a 64-bit block cipher. - - * Use AES256 and SHA256 as the default encryption and digest algorithms. - -Version 1.0.29 October 9 2016 - - * Fix UDP communication with peers with link-local IPv6 addresses. - - * Ensure compatibility with OpenSSL 1.1.0. - - * Ensure autoreconf can be run without requiring autoconf-archive. - - * Log warnings about dropped packets only at debug level 5. - -Version 1.0.28 April 10 2016 - - * Fix compilation on BSD platforms. - - * Add systemd service files. - -Version 1.0.27 April 10 2016 - - * When using Proxy, let the proxy resolve hostnames if tinc can't. - - * Fixes and improvements of the DecrementTTL option. - - * Fixed the $NAME variable in subnet-up/down scripts for the local Subnets. - - * Fixed potentially wrong checksum generation when clamping the MSS. - - * Properly choose between the system's or our own copy of getopt. - - * Fixed compiling tinc for Cygwin with MinGW installed. - - * Added support for OS X utun interfaces. - - * Documentation updates and minor fixes. - -Thanks to Vittorio Gambaletta, LunarShaddow, Florian Weik and Nathan Stratton -Treadway for their contributions to this version of tinc. - -Version 1.0.26 July 5 2015 - - * Tinc now forces glibc to reload /etc/resolv.conf for every hostname lookup. - - * Fixed --logfile without a filename on Windows. - - * Ensure tinc can be compiled when using musl libc. - -Thanks to Jo-Philipp Wich for his contribution to this version of tinc. - -Version 1.0.25 December 22 2014 - - * Documentation updates. - - * Support linking against -lresolv on Mac OS X. - - * Fix scripts on Windows when using the ScriptsInterpreter option. - - * Allow a minimum reconnect timeout to be specified. - - * Support PriorityInheritance on IPv6 sockets. - -Thanks to David Pflug, Baptiste Jonglez, Alexis Hildebrandt, Borg, Jochen Voss, -Tomislav Čohar and VittGam for their contributions to this version of tinc. - -Version 1.0.24 May 11 2014 - - * Various compiler hardening flags are enabled by default. - - * Updated support for Solaris, allowing switch mode on Solaris 11. - - * Configuration will now also be read from a conf.d directory. - - * Various updates to the documentation. - - * Tinc now forces glibc to reload /etc/resolv.conf after it receives SIGALRM. - - * Fixed a potential routing loop when IndirectData or TCPOnly is used and - broadcast packets are being sent. - - * Improved security with constant time memcmp and stricter use of OpenSSL's - RNG functions. - - * Fixed all issues found by Coverity. - -Thanks to Florent Clairambault, Vilbrekin, luckyhacky, Armin Fisslthaler, Loïc -Dachary and Steffan Karger for their contributions to this version of tinc. - -Version 1.0.23 October 19 2013 - - * Start authentication immediately on outgoing connections (useful for sslh). - - * Fixed segfault when Name = $HOST but $HOST is not set. - - * Updated the build system and the documentation. - - * Clean up child processes left over from Proxy = exec. - -Version 1.0.22 August 13 2013 - - * Fixed the combination of Mode = router and DeviceType = tap. - - * The $NAME variable is now set in subnet-up/down scripts. - - * Tinc now gives an error when unknown options are given on the command line. - - * Tinc now correctly handles a space between a short command line option and - an optional argument. +# Version 1.1pre15 September 2 2017 + +* Detect when the machine is resuming from suspension or hibernation. +* When an old PID file is found, check whether the old daemon is still alive. +* Remember scope_id for IPv6 addresses when sending UDP packets to link-local + addresses. +* Ensure compatibility with OpenSSL 1.1. +* Only log about dropped packets with debug level 5. +* Warn when trying to generate RSA keys less than 2048 bits. +* Use AES256 and SHA256 as the default encryption and digest algorithms. +* Add DeviceType = fd to support tinc on Android without requiring root. +* Support PriorityInheritance for IPv6 packets. +* Fixes for Solaris tun/tap support. +* Add a configurable expiration time for invitations. +* Store invitation data after a successful join. +* Exit gracefully when the tun/tap device is in a bad state. +* Add the LogLevel option. +* AutoConnect now actively tries to heal split networks. + +Thanks to Etienne Dechamps, Rafał Leśniak, Sean McVeigh, Vittorio Gambaletta, +Dennis Lan, Pacien Tran-Girard, Roman Savelyev, lemoer and volth for their +contributions to this version of tinc. + +# Version 1.1pre14 May 1 2016 + +* Add tinc.service back. + +# Version 1.1pre13 April 30 2016 + +* Fix BSD tun device support that was broken in 1.1pre12. +* Speed up AutoConnect when there are many host config files present without + an Address. + +# Version 1.1pre12 April 24 2016 + +* Added a "--syslog" option to force logging to syslog even if running in the + foreground. +* Fixes and improvements to the DecrementTTL function. +* Improved PMTU discovery and UDP keepalive probes. +* More efficient relaying of UDP packets through intermediate nodes. +* Improved compatibility with newer TAP-Win32 drivers. +* Added support for UPnP. +* Allow tinc to be compiled without LibreSSL or OpenSSL (this drops + compatibility with nodes running 1.0.x). +* Added a "fsck" command to check the configuration files for problems. +* Tinc "start" now checks whether the daemon really started successfully, and + displays error messages otherwise. +* Added systemd service files. +* Use the recvmmsg() function if available. +* Support ToS/Diffserv on IPv6 connections. +* Updated support for BSD tun/tap devices. +* Added support for OS X utun interfaces. +* Dropped support for Windows 2000. +* Initial support for generating a tinc-up script from an invitation. +* Many small fixes, documentation updates. + +Thanks to Etienne Dechamps, Rafał Leśniak, Vittorio Gambaletta, Martin Weinelt, +Sven-Haegar Koch, Florian Klink, LunnarShadow, Dato Simó, Jo-Philipp Wich, +Jochen Voss, Nathan Stratton Treadway, Pierre Emeriaud, xentec, Samuel +Thibault and Michael Tokarev for their contributions to this version of tinc. + +# Version 1.1pre11 December 27 2014 + +* Added a "network" command to list or switch networks. +* Switched to Ed25519 keys and the ChaCha-Poly1305 cipher for the new protocol. +* AutoConnect is now a boolean option, when enabled tinc always tries to keep + at least three meta-connections open. +* The new protocol now uses UDP much more often. +* Tinc "del" and "get" commands now return a non-zero exit code when they + don't find the requested variable. +* Updated documentation. +* Added a "DeviceStandby" option to defer running tinc-up until a working + connection is made, and which on Windows will also change the network + interface link status accordingly. +* Tinc now tells the resolver to reload /etc/resolv.conf when it receives + SIGALRM. +* Improved error messages and event loop handling on Windows. +* LocalDiscovery now uses local address learned from other nodes, and is + enabled by default. +* Added a "BroadcastSubnet" option to change the behavior of broadcast packets + in router mode. +* Added support for dotted quad notation in IPv6 (e.g. ::1.2.3.4). +* Improved format of printed Subnets, MAC and IPv6 addresses. +* Added a "--batch" option to force the tinc CLI to run in non-interactive + mode. +* Improve default Device selection on *BSD and Mac OS X. +* Allow running tinc without RSA keys. + +Thanks to Etienne Dechamps, Sven-Haegar Koch, William A. Kennington III, +Baptiste Jonglez, Alexis Hildebrandt, Armin Fisslthaler, Franz Pletz, Alexander +Ried and Saverio Proto for their contributions to this version of tinc. + +# Version 1.1pre10 February 7 2014 + +* Added a benchmark tool (sptps_speed) for the new protocol. +* Fixed a crash when using Name = $HOST while $HOST is not set. +* Use AES-256-GCM for the new protocol. +* Updated support for Solaris. +* Allow running tincd without a private ECDSA key present when + ExperimentalProtocol is not explicitly set. +* Enable various compiler hardening flags by default. +* Added support for a "conf.d" configuration directory. +* Fix tinc-gui on Windows, also allowing it to connect to a 32-bits tincd when + tinc-gui is run in a 64-bits Python environment. +* Added a "ListenAddress" option, which like BindToAddress adds more listening + address/ports, but doesn't bind to them for outgoing sockets. +* Make invitations work better when the "invite" and "join" commands are not + run interactively. +* When creating meta-connections to a node for which no Address statement is + specified, try to use addresses learned from other nodes. + +Thanks to Dennis Joachimsthaler and Florent Clairambault for their contribution +to this version of tinc. + +# Version 1.1pre9 September 8 2013 + +* The UNIX socket is now created before tinc-up is called. +* Windows users can now use any extension that is in %PATHEXT% for scripts, + not only .bat. +* Outgoing sockets are bound to the address of the listening sockets again, + when there is no ambiguity. +* Added invitation-created and invitation-accepted scripts. +* Invited nodes now learn of the Mode and Broadcast settings of the VPN. +* Joining a VPN with an invitation now also works on Windows. +* The port number tincd is listening on is now always included in the + invitation URL. +* A running tincd is now correctly informed when a new invitation has been + generated. +* Several bug fixes for the new protocol. +* Added a test suite. Thanks to Etienne Dechamps for his contribution to this version of tinc. -Version 1.0.21 April 22 2013 +# Version 1.1pre8 August 13 2013 - * Drop packets forwarded via TCP if they are too big (CVE-2013-1428). +* ExperimentalProtocol is now enabled by default. +* Added an invitation protocol that makes it easy to invite new nodes. +* Added the LocalDiscoveryAddress option to change the broadcast address used + to find local nodes. +* Limit the rate of incoming meta-connections. +* Many small bug fixes and code cleanups. + +Thanks to Etienne Dechamps and Sven-Haegar Koch for their contributions to this +version of tinc. + +# Version 1.1pre7 April 22 2013 + +* Fixed large latencies on Windows. +* Renamed the tincctl tool to tinc. +* Simplified changing the configuration using the tinc tool. +* Added a full description of the ExperimentalProtocol to the manual. +* Drop packets forwarded via TCP if they are too big (CVE-2013-1428). + +Thanks to Martin Schobert for auditing tinc and reporting the vulnerability. + +# Version 1.1pre6 February 20 2013 + +* Fixed tincd exitting immediately on Windows. +* Detect PMTU increases. +* Fixed crashes when using a SOCKS5 proxy. +* Fixed control connection when using a proxy. + +# Version 1.1pre5 January 20 2013 + +* Fixed long delays and possible hangs on Windows. +* Fixed support for the tunemu device on iOS, the UML and VDE devices. +* Small improvements to the documentation and error messages. +* Fixed broadcast packets not reaching the whole VPN. +* Tincctl now connects via a UNIX socket to the tincd on platforms that + support this. +* The PriorityInheritance option now also works in switch mode. + +# Version 1.1pre4 December 5 2012 + +* Added the "AutoConnect" option which will let tinc automatically select + which nodes to connect to. +* Improved performance of VLAN-tagged IP traffic inside the VPN. +* Ensured LocalDiscovery works with multiple BindToAddress statements and/or + IPv6-only LANs. +* Dropped dependency on libevent. +* Fixed Windows version not reading packets from the TAP adapter. + +# Version 1.1pre3 October 14 2012 + +* New experimental protocol: + * Uses 521 bit ECDSA keys for authentication. + * Uses AES-256-CTR and HMAC-SHA256. + * Always provides perfect forward secrecy. + * Used for both meta-connections and VPN packets. + * VPN packets are encrypted end-to-end. +* Many improvements to tincctl: + * "config" command shows/adds/changes configuration variables. + * "export" and "import" commands help exchange configuration files. + * "init" command sets up initial configuration files. + * "info" command shows details about a node, subnet or address. + * "log" command shows live log messages. + * Without a command it acts as a shell, with history and TAB completion. + * Improved starting/stopping tincd. + * Improved graph output. +* When trying to directly send UDP packets to a node for which multiple + addresses are known, all of them are tried. +* Many small fixes, code cleanups and documentation updates. + +# Version 1.1pre2 July 17 2011 + +* .cookie files are renamed to .pid files, which are compatible with 1.0.x. +* Experimental protocol enhancements that can be enabled with the option + ExperimentalProtocol = yes: + + * Ephemeral ECDH key exchange will be used for both the meta protocol and + UDP session keys. + * Key exchanges are signed with ECDSA. + * ECDSA public keys are automatically exchanged after RSA authentication if + nodes do not know each other's ECDSA public key yet. + +# Version 1.1pre1 June 25 2011 + +* Control interface allows control of a running tinc daemon. Used by: + * tincctl, a commandline utility + * tinc-gui, a preliminary GUI implemented in Python/wxWidgets +* Code cleanups and reorganization. +* Repleacable cryptography backend, currently supports OpenSSL and libgcrypt. +* Use libevent to handle I/O events and timeouts. +* Use splay trees instead of AVL trees to manage internal datastructures. + +Thanks to Scott Lamb and Sven-Haegar Koch for their contributions to this +version of tinc. + +# Version 1.0.22 August 13 2013 + +* Fixed the combination of Mode = router and DeviceType = tap. +* The $NAME variable is now set in subnet-up/down scripts. +* Tinc now gives an error when unknown options are given on the command line. +* Tinc now correctly handles a space between a short command line option and + an optional argument. + +Thanks to Etienne Dechamps for his contribution to this version of tinc. + +# Version 1.0.21 April 22 2013 + +* Drop packets forwarded via TCP if they are too big (CVE-2013-1428). Thanks to Martin Schobert for auditing tinc and reporting this vulnerability. -Version 1.0.20 March 03 2013 +# Version 1.0.20 March 03 2013 - * Use /dev/tap0 by default on FreeBSD and NetBSD when using switch mode. - - * Minor improvements and clarifications in the documentation. - - * Allow tinc to be cross-compiled with Android's NDK. - - * The discovered PMTU is now also applied to VLAN tagged traffic. - - * The LocalDiscovery option now makes use of all addresses tinc is bound to. - - * Fixed support for tunemu on iOS devices. - - * The PriorityInheritance option now also works with switch mode. - - * Fixed tinc crashing when using a SOCKS5 proxy. +* Use /dev/tap0 by default on FreeBSD and NetBSD when using switch mode. +* Minor improvements and clarifications in the documentation. +* Allow tinc to be cross-compiled with Android's NDK. +* The discovered PMTU is now also applied to VLAN tagged traffic. +* The LocalDiscovery option now makes use of all addresses tinc is bound to. +* Fixed support for tunemu on iOS devices. +* The PriorityInheritance option now also works with switch mode. +* Fixed tinc crashing when using a SOCKS5 proxy. Thanks to Mesar Hameed, Vilbrekin and Martin Schürrer for their contributions to this version of tinc. -Version 1.0.19 June 25 2012 - - * Allow :: notation in IPv6 Subnets. - - * Add support for systemd style socket activation. - - * Allow environment variables to be used for the Name option. - - * Add basic support for SOCKS proxies, HTTP proxies, and proxying through an - external command. - -Thanks to Anthony G. Basile and Michael Tokarev for their contributions to -this version of tinc. - -Version 1.0.18 March 25 2012 - - * Fixed IPv6 in switch mode by turning off DecrementTTL by default. - - * Allow a port number to be specified in BindToAddress, which also allows tinc - to listen on multiple ports. - - * Add support for multicast communication with UML/QEMU/KVM. - -Version 1.0.17 March 10 2012 - - * The DeviceType option can now be used to select dummy, raw socket, UML and - VDE devices without needing to recompile tinc. - - * Allow multiple BindToAddress statements. - - * Decrement TTL value of IPv4 and IPv6 packets. - - * Add LocalDiscovery option allowing tinc to detect peers that are behind the - same NAT. - - * Accept Subnets passed with the -o option when StrictSubnets = yes. - - * Disabling old RSA keys when generating new ones now also works properly on - Windows. - -Thanks to Nick Hibma for his contribution to this version of tinc. - -Version 1.0.16 July 23 2011 - - * Fixed a performance issue with TCP communication under Windows. - - * Fixed code that, during network outages, would cause tinc to exit when it - thought two nodes with identical Names were on the VPN. - -Version 1.0.15 June 24 2011 - - * Improved logging to file. - - * Reduced amount of process wakeups on platforms which support pselect(). - - * Fixed ProcessPriority option under Windows. - -Version 1.0.14 May 8 2011 - - * Fixed reading configuration files that do not end with a newline. Again. - - * Allow arbitrary configuration options being specified on the command line. - - * Allow all options in both tinc.conf and the local host config file. - - * Configurable replay window, UDP send and receive buffers for performance tuning. - - * Try harder to get UDP communication back after falling back to TCP. - - * Initial support for attaching tinc to a VDE switch. - - * DragonFly BSD support. - - * Allow linking with OpenSSL 1.0.0. - - Thanks to Brandon Black, Julien Muchembled, Michael Tokarev, Rumko and Timothy - Redaelli for their contributions to this version of tinc. - -Version 1.0.13 Apr 11 2010 - - * Allow building tinc without LZO and/or Zlib. - - * Clamp MSS of TCP packets in both directions. - - * Experimental StrictSubnets, Forwarding and DirectOnly options, - giving more control over information and packets received from/sent to other - nodes. - - * Ensure tinc never sends symbolic names for ports over the wire. - -Version 1.0.12 Feb 3 2010 - - * Really allow fast roaming of hosts to other nodes in a switched VPN. - - * Fixes missing or incorrect environment variables when calling host-up/down - and subnet-up/down scripts in some cases. - - * Allow port to be specified in Address statements. - - * Clamp MSS of TCP packets to the discovered path MTU. - - * Let two nodes behind NAT learn each others current UDP address and port via - a third node, potentially allowing direct communications in a similar way to - STUN. - -Version 1.0.11 Nov 1 2009 - - * Fixed potential crash when the HUP signal is sent. - - * Fixes handling of weighted Subnets in switch and hub modes, preventing - unnecessary broadcasts. - - * Works around a MinGW bug that caused packets to Windows nodes to always be - sent via TCP. - - * Improvements to the PMTU discovery code, especially on Windows. - - * Use UDP again in certain cases where 1.0.10 was too conservative and fell - back to TCP unnecessarily. - - * Allow fast roaming of hosts to other nodes in a switched VPN. - -Version 1.0.10 Oct 18 2009 - - * Fixed potential crashes during shutdown and (in rare conditions) when other - nodes disconnected from the VPN. - - * Improved NAT handling: tinc now copes with mangled port numbers, and will - automatically fall back to TCP if direct UDP connection between nodes is not - possible. The TCPOnly option should not have to be used anymore. - - * Allow configuration files with CRLF line endings to be read on UNIX. - - * Disable old RSA keys when generating new ones, and raise the default size of - new RSA keys to 2048 bits. - - * Many fixes in the path MTU discovery code, especially when Compression is - being used. - - * Tinc can now drop privileges and/or chroot itself. - - * The TunnelServer code now just ignores information from clients instead of - disconnecting them. - - * Improved performance on Windows by using the new ProcessPriority option and - by making the handling of packets received from the TAP-Win32 adapter more - efficient. - - * Code cleanups: tinc now follows the C99 standard, copyright headers have - been updated to include patch authors, checkpoint tracing and localisation - features have been removed. - - * Support for (jailbroken) iPhone and iPod Touch has been added. - - Thanks to Florian Forster, Grzegorz Dymarek and especially Michael Tokarev for - their contributions to this version of tinc. - -Version 1.0.9 Dec 26 2008 - - * Fixed tinc as a service under Windows 2003. - - * Fixed reading configuration files that do not end with a newline. - - * Fixed crashes in situations where hostnames could not be resolved or hosts - would disconnect at the same time as session keys were exchanged. - - * Improved default settings of tun and tap devices on BSD platforms. - - * Make IPv6 sockets bind only to IPv6 on Linux. - - * Enable path MTU discovery by default. - - * Fixed a memory leak that occurred when connections were closed. - - Thanks to Max Rijevski for his contributions to this version of tinc. - -Version 1.0.8 May 16 2007 - - * Fixed some memory and resource leaks. - - * Made network sockets non-blocking under Windows. - - Thanks to Scott Lamb and "dnk" for their contributions to this version of tinc. - -Version 1.0.7 Jan 5 2007 - - * Fixed a bug that caused slow network speeds on Windows. - - * Fixed a bug that caused tinc unable to write packets to the tun device on - OpenBSD. - -Version 1.0.6 Dec 18 2006 - - * More flexible detection of the LZO libraries when compiling. - - * Fixed a bug where broadcasts in switch and hub modes sometimes would not - work anymore when part of the VPN had become disconnected from the rest. - -version 1.0.5 Nov 14 2006 - - * Lots of small fixes. - - * Broadcast packets no longer grow in size with each hop. This should - fix switch mode (again). - - * Generic host-up and host-down scripts. - - * Optionally dump graph in graphviz format to a file or a script. - - * Support LZO 2.0 and later. - - Thanks to Scott Lamb for his contributions to this version of tinc. - -version 1.0.4 May 4 2005 - - * Fix switch and hub modes. - - * Optionally start scripts when a Subnet becomes (un)reachable. - -version 1.0.3 Nov 11 2004 +# Version 1.0.19 June 25 2012 + +* Allow :: notation in IPv6 Subnets. +* Add support for systemd style socket activation. +* Allow environment variables to be used for the Name option. +* Add basic support for SOCKS proxies, HTTP proxies, and proxying through an + external command. + +# Version 1.0.18 March 25 2012 + +* Fixed IPv6 in switch mode by turning off DecrementTTL by default. +* Allow a port number to be specified in BindToAddress, which also allows tinc + to listen on multiple ports. +* Add support for multicast communication with UML/QEMU/KVM. + +# Version 1.0.17 March 10 2012 + +* The DeviceType option can now be used to select dummy, raw socket, UML and + VDE devices without needing to recompile tinc. +* Allow multiple BindToAddress statements. +* Decrement TTL value of IPv4 and IPv6 packets. +* Add LocalDiscovery option allowing tinc to detect peers that are behind the + same NAT. +* Accept Subnets passed with the -o option when StrictSubnets = yes. +* Disabling old RSA keys when generating new ones now also works properly on + Windows. + +# Version 1.0.16 July 23 2011 + +* Fixed a performance issue with TCP communication under Windows. +* Fixed code that, during network outages, would cause tinc to exit when it + thought two nodes with identical Names were on the VPN. + +# Version 1.0.15 June 24 2011 + +* Improved logging to file. +* Reduced amount of process wakeups on platforms which support pselect(). +* Fixed ProcessPriority option under Windows. + + Thanks to Loïc Grenié for his contribution to this version of tinc. + +# Version 1.0.14 May 8 2011 + +* Fixed reading configuration files that do not end with a newline. Again. +* Allow arbitrary configuration options being specified on the command line. +* Allow all options in both tinc.conf and the local host config file. +* Configurable replay window, UDP send and receive buffers for performance tuning. +* Try harder to get UDP communication back after falling back to TCP. +* Initial support for attaching tinc to a VDE switch. +* DragonFly BSD support. +* Allow linking with OpenSSL 1.0.0. + +Thanks to Brandon Black, Julien Muchembled, Michael Tokarev, Rumko and Timothy +Redaelli for their contributions to this version of tinc. + +# Version 1.0.13 Apr 11 2010 + +* Allow building tinc without LZO and/or Zlib. +* Clamp MSS of TCP packets in both directions. +* Experimental StrictSubnets, Forwarding and DirectOnly options, + giving more control over information and packets received from/sent to other + nodes. +* Ensure tinc never sends symbolic names for ports over the wire. + +# Version 1.0.12 Feb 3 2010 + +* Really allow fast roaming of hosts to other nodes in a switched VPN. +* Fixes missing or incorrect environment variables when calling host-up/down + and subnet-up/down scripts in some cases. +* Allow port to be specified in Address statements. +* Clamp MSS of TCP packets to the discovered path MTU. +* Let two nodes behind NAT learn each others current UDP address and port via + a third node, potentially allowing direct communications in a similar way to + STUN. + +# Version 1.0.11 Nov 1 2009 + +* Fixed potential crash when the HUP signal is sent. +* Fixes handling of weighted Subnets in switch and hub modes, preventing + unnecessary broadcasts. +* Works around a MinGW bug that caused packets to Windows nodes to always be + sent via TCP. +* Improvements to the PMTU discovery code, especially on Windows. +* Use UDP again in certain cases where 1.0.10 was too conservative and fell + back to TCP unnecessarily. +* Allow fast roaming of hosts to other nodes in a switched VPN. + +# Version 1.0.10 Oct 18 2009 + +* Fixed potential crashes during shutdown and (in rare conditions) when other + nodes disconnected from the VPN. +* Improved NAT handling: tinc now copes with mangled port numbers, and will + automatically fall back to TCP if direct UDP connection between nodes is not + possible. The TCPOnly option should not have to be used anymore. +* Allow configuration files with CRLF line endings to be read on UNIX. +* Disable old RSA keys when generating new ones, and raise the default size of + new RSA keys to 2048 bits. +* Many fixes in the path MTU discovery code, especially when Compression is + being used. +* Tinc can now drop privileges and/or chroot itself. +* The TunnelServer code now just ignores information from clients instead of + disconnecting them. +* Improved performance on Windows by using the new ProcessPriority option and + by making the handling of packets received from the TAP-Win32 adapter more + efficient. +* Code cleanups: tinc now follows the C99 standard, copyright headers have + been updated to include patch authors, checkpoint tracing and localisation + features have been removed. +* Support for (jailbroken) iPhone and iPod Touch has been added. + +Thanks to Florian Forster, Grzegorz Dymarek and especially Michael Tokarev for +their contributions to this version of tinc. + +# Version 1.0.9 Dec 26 2008 + +* Fixed tinc as a service under Windows 2003. +* Fixed reading configuration files that do not end with a newline. +* Fixed crashes in situations where hostnames could not be resolved or hosts + would disconnect at the same time as session keys were exchanged. +* Improved default settings of tun and tap devices on BSD platforms. +* Make IPv6 sockets bind only to IPv6 on Linux. +* Enable path MTU discovery by default. +* Fixed a memory leak that occurred when connections were closed. + +Thanks to Max Rijevski for his contributions to this version of tinc. + +# Version 1.0.8 May 16 2007 + +* Fixed some memory and resource leaks. +* Made network sockets non-blocking under Windows. + +Thanks to Scott Lamb and "dnk" for their contributions to this version of tinc. + +# Version 1.0.7 Jan 5 2007 + +* Fixed a bug that caused slow network speeds on Windows. +* Fixed a bug that caused tinc unable to write packets to the tun device on + OpenBSD. + +# Version 1.0.6 Dec 18 2006 + +* More flexible detection of the LZO libraries when compiling. +* Fixed a bug where broadcasts in switch and hub modes sometimes would not + work anymore when part of the VPN had become disconnected from the rest. + +# Version 1.0.5 Nov 14 2006 + +* Lots of small fixes. +* Broadcast packets no longer grow in size with each hop. This should + fix switch mode (again). +* Generic host-up and host-down scripts. +* Optionally dump graph in graphviz format to a file or a script. +* Support LZO 2.0 and later. + +Thanks to Scott Lamb for his contributions to this version of tinc. + +# Version 1.0.4 May 4 2005 + +* Fix switch and hub modes. +* Optionally start scripts when a Subnet becomes (un)reachable. + +# Version 1.0.3 Nov 11 2004 * Show error message when failing to write a PID file. - * Ignore spaces at end of lines in config files. - * Fix handling of late packets. - * Unify BSD tun/tap device handling. This allows IPv6 on tun devices and anything on tap devices as long as the underlying OS supports it. - * Handle IPv6 on Solaris tun devices. - * Allow tinc to work properly under Windows XP SP2. - * Allow VLAN tagged Ethernet frames in switch and hub mode. - * Experimental PMTUDiscovery, TunnelServer and BlockingTCP options. -version 1.0.2 Nov 8 2003 +# Version 1.0.2 Nov 8 2003 * Fix address and hostname resolving under Windows. - * Remove warnings about non-existing scripts and unsupported address families. - * Use the event logger under Windows. - * Fix quoting of filenames and command line arguments under Windows. - * Strict checks for length incoming network packets and return values of cryptographic functions, - * Fix a bug in metadata handling that made the tinc daemon abort. -version 1.0.1 Aug 14 2003 +# Version 1.0.1 Aug 14 2003 * Allow empty lines in config files. - * Fix handling of spaces and backslashes in filenames under native Windows. - * Allow scripts to be executed under native Windows. - * Update documentation, make it less Linux specific. -version 1.0 Aug 4 2003 +# Version 1.0 Aug 4 2003 * Lots of small bugfixes and code cleanups. - * Throughput doubled and latency reduced. - * Added support for LZO compression. - * No need to set MAC address or disable ARP anymore. - * Added support for Windows 2000 and XP, both natively and in a Cygwin environment. -version 1.0pre8 Sep 16 2002 +# Version 1.0pre8 Sep 16 2002 * More fixes for subnets with prefixlength undivisible by 8. - * Added support for NetBSD and MacOS/X. - * Switched from undirected graphs to directed graphs to avoid certain race conditions and improve scalability. - * Generalized broadcasting and forwarding of protocol messages. - * Cleanup of source code. - -version 1.0pre7 Apr 7 2002 +# Version 1.0pre7 Apr 7 2002 * Don't do blocking read()s when getting a signal. - * Remove RSA key checking code, since it sometimes thinks perfectly good RSA keys are bad. - * Fix handling of subnets when prefixlength isn't divisible by 8. - -version 1.0pre6 Mar 27 2002 +# Version 1.0pre6 Mar 27 2002 * Improvement of redundant links: - * Non-blocking connects. - * Protocol broadcast messages can no longer go into an infinite loop. - * Graph algorithm updated to look harder for direct connections. - * Good support for routing IPv6 packets over the VPN. Works on Linux, FreeBSD, possibly OpenBSD but not on Solaris. - * Support for tunnels over IPv6 networks. Works on all supported operating systems. - * Optional compression of UDP connections using zlib. - * Optionally let UDP connections inherit TOS field of tunneled packets. - * Optionally start scripts when certain hosts become (un)reachable. - -version 1.0pre5 Feb 9 2002 +# Version 1.0pre5 Feb 9 2002 * Security enhancements: - * Added sequence number and optional message authentication code to the packets. - * Configurable encryption cipher and digest algorithms. - * More robust handling of dis- and reconnects. - * Added a "switch" and a "hub" mode to allow bridging setups. - * Preliminary support for routing of IPv6 packets. - * Supports Linux, FreeBSD, OpenBSD and Solaris. - -It looks like this might be the last release before 1.0. - - -version 1.0pre4 Jan 17 2001 +# Version 1.0pre4 Jan 17 2001 * Updated documentation; the documentation now reflects the configuration as it is. - * Some internal changes to make tinc scale better for large networks, such as using AVL trees instead of linked lists for the - connection list. - + connection list. * RSA keys can be stored in separate files if needed. See the documentation for more information. +* Tinc has now been reported to run on Linux PowerPC and FreeBSD x86. -* tinc has now been reported to run on Linux PowerPC and FreeBSD x86. - - - -version 1.0pre3 Oct 31 2000 +# Version 1.0pre3 Oct 31 2000 * The protocol has been redesigned, and although some details are still under discussion, this is secure. Care has been taken to resist most, if not all, attacks. - * Unfortunately this protocol is not compatible with earlier versions, nor are earlier versions compatible with this version. Because the older protocol has huge security flaws, we feel that not implementing backwards compatibility is justified. - * Some data about the protocol: - * It uses public/private RSA keys for authentication (this is the actual fix for the security hole). - * All cryptographic functions have been taken out of tinc, instead it uses the OpenSSL library functions. - * Offers support for multiple subnets per tinc daemon. - * New is also the support for the universal tun/tap device. This means better portability to FreeBSD and Solaris. - -* tinc is tested to compile on Solaris, Linux x86, Linux alpha. - -* tinc now uses the OpenSSL library for cryptographic operations. +* Tinc is tested to compile on Solaris, Linux x86, Linux alpha. +* Tinc now uses the OpenSSL library for cryptographic operations. More information on getting and installing OpenSSL is in the manual. This also means that the GMP library is no longer required. - * Further, thanks to Enrique Zanardi, we have Spanish messages; Matias Carrasco provided us with a Spanish translation of the manual. +# Version 1.0pre2 May 31 2000 -What still needs to be done before 1.0: +* This version has been internationalized; and a Dutch translation has + been included. +* Two configuration variables have been added: + * VpnMask - the IP network mask for the entire VPN, not just our + subnet (as given by MyVirtualIP). The Redhat and Debian packages + use this variable in their system startup scripts, but it is + ignored by tinc. + * Hostnames - if set to `yes', look up the names of IP addresses + trying to connect to us. Default set to `no', to prevent lockups + during lookups. +* The system startup scripts for Debian and Redhat use + /etc/tinc/nets.boot to find out which networks need to be started + during system boot. +* Fixes to prevent denial of service attacks by sending random data + after connecting (and even when the connection has been established), + either random garbage or just nonsensical protocol fields. +* Tinc will retry to connect upon startup, does not quit if it doesn't + work the first time. +* Hosts that are disconnected implicitly if we lose a connection get + deleted from the internal list, to prevent hogging each other with + add and delete requests when the connection is restored. -* Documentation. Especially since the protocol has changed, and a lot - of configuration directives have been added. +# Version 1.0pre1 May 12 2000 +* New meta-protocol +* Various other bugfixes +* Documentation updates +# Version 0.3.3 Feb 9 2000 +* Fixed bug that made tinc stop working with latest kernels +* Updated the manual -version 1.0pre2 May 31 2000 +# Version 0.3.2 Nov 12 1999 -* This version has been internationalized; and a Dutch translation has - been included. - -* Two configuration variables have been added: - * VpnMask - the IP network mask for the entire VPN, not just our - subnet (as given by MyVirtualIP). The Redhat and Debian packages - use this variable in their system startup scripts, but it is - ignored by tinc. - * Hostnames - if set to `yes', look up the names of IP addresses - trying to connect to us. Default set to `no', to prevent lockups - during lookups. - -* The system startup scripts for Debian and Redhat use - /etc/tinc/nets.boot to find out which networks need to be started - during system boot. - -* Fixes to prevent denial of service attacks by sending random data - after connecting (and even when the connection has been established), - either random garbage or just nonsensical protocol fields. - -* tinc will retry to connect upon startup, does not quit if it doesn't - work the first time. - -* Hosts that are disconnected implicitly if we lose a connection get - deleted from the internal list, to prevent hogging eachother with - add and delete requests when the connection is restored. - - -What still needs to be done before 1.0: - -* Documentation. -* Failover ConnectTo lines, try another one if the first doesn't work. +* No more `Invalid filedescriptor' when working with multiple + connections. +* Forward unknown packets to uplink. +# Version 0.3.1 Oct 20 1999 +* Fixed a bug where tinc would exit without a trace. +# Version 0.3 Aug 20 1999 -version 1.0pre1 May 12 2000 - * New meta-protocol - * Various other bugfixes - * Documentation updates +* Pings now work immediately. +* All packet sizes get transmitted correctly. -version 0.3.3 Feb 9 2000 - * Fixed bug that made tinc stop working with latest kernels (Guus - Sliepen) - * Updated the manual +# Version 0.2.26 Aug 15 1999 -version 0.3.2 Nov 12 1999 - * no more `Invalid filedescriptor' when working with multiple - connections - * forward unknown packets to uplink +* Fixed some remaining bugs. +* --sysconfdir works with configure. +* Last version before 0.3. -version 0.3.1 Oct 20 1999 - * fixed a bug where tinc would exit without a trace +# Version 0.2.25 Aug 8 1999 -version 0.3 Aug 20 1999 - * pings now work immediately - * all packet sizes get transmitted correctly +* Improved stability, going towards 0.3 now. -version 0.2.26 Aug 15 1999 - * fixed some remaining bugs - * --sysconfdir works with configure - * last version before 0.3 +# Version 0.2.24 Aug 7 1999 -version 0.2.25 Aug 8 1999 - * improved stability, going towards 0.3 now. +* Added key aging, there's a new config variable, KeyExpire. +* Updated man and info pages. -version 0.2.24 Aug 7 1999 - * added key aging, there's a new config variable, KeyExpire. - * updated man and info pages +# Version 0.2.23 Aug 5 1999 -version 0.2.23 Aug 5 1999 - * all known bugs fixed, this is a candidate for 0.3 +* All known bugs fixed, this is a candidate for 0.3. -version 0.2.22 Apr 11 1999 - * multiconnection thing is now working nearly perfect :) +# Version 0.2.22 Apr 11 1999 -version 0.2.21 Apr 10 1999 - * You shouldn't notice a thing, but a lot has changed wrt key +* Multiconnection thing is now working nearly perfect :) + +# Version 0.2.21 Apr 10 1999 + +* You shouldn't notice a thing, but a lot has changed wrt key management - except that it refuses to talk to versions < 0.2.20 -version 0.2.20 +# Version 0.2.19 Apr 3 1999 -version 0.2.19 Apr 3 1999 - * don't install a libcipher.so +* Don't install a libcipher.so. -version 0.2.18 Apr 3 1999 - * blowfish library dynamically loaded upon execution - * included Eric Young's IDEA library +# Version 0.2.18 Apr 3 1999 -version 0.2.17 Apr 1 1999 - * tincd now re-executes itself in case of a segmentation fault. +* Blowfish library dynamically loaded upon execution. +* Included Eric Young's IDEA library. -version 0.2.16 Apr 1 1999 - * wrote tincd.conf(5) man page, which still needs a lot of work. - * config file now accepts and tolerates spaces, and any integer base -for integer variables, and better error reporting. See -doc/tincd.conf.sample for an example. +# Version 0.2.17 Apr 1 1999 -version 0.2.15 Mar 29 1999 - * fixed bugs +* Tincd now re-executes itself in case of a segmentation fault. -version 0.2.14 Feb 10 1999 - * added --timeout flag and PingTimeout configuration - * did some first syslog cleanup work +# Version 0.2.16 Apr 1 1999 -version 0.2.13 Jan 23 1999 - * bugfixes +* Wrote tincd.conf(5) man page, which still needs a lot of work. +* Config file now accepts and tolerates spaces, and any integer base + for integer variables, and better error reporting. See + doc/tincd.conf.sample for an example. -version 0.2.12 Jan 23 1999 - * fixed nauseating bug so that it would crash whenever a connection -got lost +# Version 0.2.15 Mar 29 1999 -version 0.2.11 Jan 22 1999 - * framework for multiple connections has been done - * simple manpage for tincd +* Fixed bugs. -version 0.2.10 Jan 18 1999 - * passphrase support added +# Version 0.2.14 Feb 10 1999 -version 0.2.9 Jan 13 1999 - * bugs fixed. +* Added --timeout flag and PingTimeout configuration. +* Did some first syslog cleanup work. -version 0.2.8 Jan 11 1999 - * a reworked protocol version - * a ping/pong system - * more reliable networking code - * automatic reconnection - * still does not work with more than one connection :) - * strips MAC addresses before sending, so there's less overhead, and -less redundancy +# Version 0.2.13 Jan 23 1999 -version 0.2.7 Jan 3 1999 - * several updates to make extending more easy. +* Bugfixes. -version 0.2.6 Dec 20 1998 - * Point-to-Point connections have been established, including -blowfish encryption and a secret key-exchange. +# Version 0.2.12 Jan 23 1999 -version 0.2.5 Dec 16 1998 - * Project renamed to tinc, in honour of TINC. +* Fixed nauseating bug so that it would crash whenever a connection + got lost. -version 0.2.4 Dec 16 1998 - * now it really does ;) +# Version 0.2.11 Jan 22 1999 -version 0.2.3 Nov 24 1998 - * it sort of works now +* Framework for multiple connections has been done. +* Simple manpage for tincd. -version 0.2.2 Nov 20 1998 - * uses GNU gmp. +# Version 0.2.10 Jan 18 1999 -version 0.2.1 Nov 14 1998 +* Passphrase support added. - * Bare version. +# Version 0.2.9 Jan 13 1999 + +* Bugs fixed. + +# Version 0.2.8 Jan 11 1999 + +* A reworked protocol version. +* A ping/pong system. +* More reliable networking code. +* Automatic reconnection. +* Still does not work with more than one connection :) +* Strips MAC addresses before sending, so there's less overhead, and + less redundancy. + +# Version 0.2.7 Jan 3 1999 + +* Several updates to make extending more easy. + +# Version 0.2.6 Dec 20 1998 + +* Point-to-Point connections have been established, including + Blowfish encryption and a secret key-exchange. + +# Version 0.2.5 Dec 16 1998 + +* Project renamed to tinc, in honour of TINC. + +# Version 0.2.4 Dec 16 1998 + +* Now it really does ;) + +# Version 0.2.3 Nov 24 1998 + +* It sort of works now. + +# Version 0.2.2 Nov 20 1998 + +* Uses GNU gmp. + +# Version 0.2.1 Nov 14 1998 + +* Bare version. diff --git a/README b/README index 127cde2..f21c245 100644 --- a/README +++ b/README @@ -1,11 +1,7 @@ -This is the README file for tinc version 1.0.36. Installation +This is the README file for tinc version 1.1pre18. Installation instructions may be found in the INSTALL file. -tinc is Copyright (C) 1998-2019 by: - -Ivo Timmermans, -Guus Sliepen , -and others. +tinc is Copyright © 1998-2021 Ivo Timmermans, Guus Sliepen , and others. For a complete list of authors see the AUTHORS file. @@ -15,119 +11,94 @@ the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING for more details. +This is a pre-release +--------------------- + +Please note that this is NOT a stable release. Until version 1.1.0 is released, +please use one of the 1.0.x versions if you need a stable version of tinc. + +Although tinc 1.1 will be protocol compatible with tinc 1.0.x, the +functionality of the tinc program may still change, and the control socket +protocol is not fixed yet. + + Security statement ------------------ -In August 2000, we discovered the existence of a security hole in all versions -of tinc up to and including 1.0pre2. This had to do with the way we exchanged -keys. Since then, we have been working on a new authentication scheme to make -tinc as secure as possible. The current version uses the OpenSSL library and -uses strong authentication with RSA keys. +This version uses an experimental and unfinished cryptographic protocol. Use it +at your own risk. -On the 29th of December 2001, Jerome Etienne posted a security analysis of tinc -1.0pre4. Due to a lack of sequence numbers and a message authentication code -for each packet, an attacker could possibly disrupt certain network services or -launch a denial of service attack by replaying intercepted packets. The current -version adds sequence numbers and message authentication codes to prevent such -attacks. - -On September the 15th of 2003, Peter Gutmann contacted us and showed us a -writeup describing various security issues in several VPN daemons. He showed -that tinc lacks perfect forward security, the connection authentication could -be done more properly, that the sequence number we use as an IV is not the best -practice and that the default length of the HMAC for packets is too short in -his opinion. We do not know of a way to exploit these weaknesses, but these -issues are being addressed in the tinc 1.1 branch. - -The Sweet32 attack affects versions of tinc prior to 1.0.30. - -On September 6th, 2018, Michael Yonly contacted us and provided -proof-of-concept code that allowed a remote attacker to create an -authenticated, one-way connection with a node, and also that there was a +When connecting to nodes that use the legacy protocol used in tinc 1.0, be +aware that any security issues in tinc 1.0 will apply to tinc 1.1 as well. On +September 6th, 2018, Michael Yonly contacted us and provided proof-of-concept +code that allowed a remote attacker to create an authenticated, one-way +connection with a node using the legacy protocol, and also that there was a possibility for a man-in-the-middle to force UDP packets from a node to be sent in plaintext. The first issue was trivial to exploit on tinc versions prior to 1.0.30, but the changes in 1.0.30 to mitigate the Sweet32 attack made this -weakness much harder to exploit. These issues have been fixed in tinc 1.0.35. -The new protocol in the tinc 1.1 branch is not susceptible to these issues. - -Cryptography is a hard thing to get right. We cannot make any -guarantees. Time, review and feedback are the only things that can -prove the security of any cryptographic product. If you wish to review -tinc or give us feedback, you are strongly encouraged to do so. +weakness much harder to exploit. These issues have been fixed in tinc 1.0.35 +and tinc 1.1pre17. The new protocol in the tinc 1.1 branch is not susceptible +to these issues. However, be aware that SPTPS is only used between nodes +running tinc 1.1pre* or later, and in a VPN with nodes running different +versions, the security might only be as good as that of the oldest version. Compatibility ------------- -Version 1.0.35 is compatible with 1.0pre8, 1.0 and later, but not with older -versions of tinc. Note that since version 1.0.30, tinc requires all nodes in -the VPN to be compiled with a version of LibreSSL or OpenSSL that supports the -AES256 and SHA256 algorithms. +Version 1.1pre18 is compatible with 1.0pre8, 1.0 and later, but not with older +versions of tinc. + +When the ExperimentalProtocol option is used, tinc is still compatible with +1.0.X, 1.1pre11 and later, but not with any version between 1.1pre1 and +1.1pre10. Requirements ------------ -The OpenSSL library is used for all cryptographic functions. You can find it at -https://www.openssl.org/. You will need version 1.0.1 or later with support for -AES256 and SHA256 enabled. If this library is not installed on your system, the -configure script will fail. The manual in doc/tinc.texi contains more detailed -information on how to install this library. Alternatively, you may also use the -LibreSSL library. +In order to compile tinc, you will need a GNU C compiler environment. Please +ensure you have the latest stable versions of all the required libraries: -The zlib library is used for optional compression. You can -find it at https://zlib.net/. Because of a possible exploit in -earlier versions we recommend that you download version 1.1.4 or later. +- LibreSSL (http://www.libressl.org/) or OpenSSL (https://openssl.org/) version 1.0.0 or later. -The LZO library is also used for optional compression. You can -find it at https://www.oberhumer.com/opensource/lzo/. +The following libraries are used by default, but can be disabled if necessary: -In order to compile tinc, you will need a C99 compliant compiler. +- zlib (https://zlib.net/) +- LZO (https://www.oberhumer.com/opensource/lzo/) +- ncurses (https://invisible-island.net/ncurses/) +- readline (https://cnswww.cns.cwru.edu/php/chet/readline/rltop.html) Features -------- -This version of tinc supports multiple virtual networks at once. To -use this feature, you may supply a netname via the -n or --net -options. The standard locations for the config files will then be -/etc/tinc//. +Tinc is a peer-to-peer VPN daemon that supports VPNs with an arbitrary number +of nodes. Instead of configuring tunnels, you give tinc the location and +public key of a few nodes in the VPN. After making the initial connections to +those nodes, tinc will learn about all other nodes on the VPN, and will make +connections automatically. When direct connections are not possible, data will +be forwarded by intermediate nodes. -tincd regenerates its encryption key pairs. It does this on the first -activity after the keys have expired. This period is adjustable in the -configuration file, and the default time is 3600 seconds (one hour). +Tinc 1.1 support two protocols. The first is a legacy protocol that provides +backwards compatibility with tinc 1.0 nodes, and which by default uses 2048 bit +RSA keys for authentication, and encrypts traffic using AES256 in CBC mode +and HMAC-SHA256. The second is a new protocol which uses Curve25519 keys for +authentication, and encrypts traffic using Chacha20-Poly1305, and provides +forward secrecy. -This version supports multiple subnets at once. They are also sorted -on subnet mask size. This means that it is possible to have -overlapping subnets on the VPN, as long as their subnet mask sizes -differ. +Tinc fully supports IPv6. -Since pre5, tinc can operate in several routing modes. The default mode, -"router", works exactly like the older version, and uses Subnet lines to -determine the destination of packets. The other two modes, "switch" and "hub", -allow the tinc daemons to work together like a single network switch or hub. -This is useful for bridging networks. The latter modes only work properly on -Linux, FreeBSD and Windows. - -The algorithms used for encryption and generating message authentication codes -can now be changed in the configuration files. All cipher and digest algorithms -supported by OpenSSL can be used. Useful ciphers are "blowfish" (default), -"bf-ofb", "des", "des3", et cetera. Useful digests are "sha1" (default), "md5", -et cetera. - -Support for routing IPv6 packets has been added. Just add Subnet lines with -IPv6 addresses (without using :: abbreviations) and use ifconfig or ip (from -the iproute package) to give the virtual network interface corresponding IPv6 -addresses. tinc does not provide autoconfiguration for IPv6 hosts. Consider -using radvd or zebra if you need it. - -It is also possible to make tunnels to other tinc daemons over IPv6 networks, -if the operating system supports IPv6. tinc will automatically use both IPv6 -and IPv4 when available, but this can be changed by adding the option -"AddressFamily = ipv4" or "AddressFamily = ipv6" to the tinc.conf file. +Tinc can operate in several routing modes. In the default mode, "router", every +node is associated with one or more IPv4 and/or IPv6 Subnets. The other two +modes, "switch" and "hub", let the tinc daemons work together to form a virtual +Ethernet network switch or hub. Normally, when started tinc will detach and run in the background. In a native Windows environment this means tinc will install itself as a service, which will -restart after reboots. To prevent tinc from detaching or running as a service, +restart after reboots. To prevent tinc from detaching or running as a service, use the -D option. +The status of the VPN can be queried using the "tinc" command, which connects +to a running tinc daemon via a control connection. The same tool also makes it +easy to start and stop tinc, and to change its configuration. diff --git a/README.android b/README.android index 7925bdb..7d8e853 100644 --- a/README.android +++ b/README.android @@ -1,25 +1,23 @@ -Quick how-to cross compile tinc for android (done from $HOME/android/): +Quick how-to cross compile tinc for Android (done from $HOME/android/): -- Download android NDK and setup local ARM toolchain: -wget http://dl.google.com/android/ndk/android-ndk-r9d-linux-x86.tar.bz2 -tar xfj android-ndk-r9d-linux-x86.tar.bz2 -./android-ndk-r9d/build/tools/make-standalone-toolchain.sh --platform=android-5 --install-dir=/tmp/my-android-toolchain +- Download Android NDK and setup local ARM toolchain: -- Download and cross-compile openSSL for ARM: -wget http://www.openssl.org/source/openssl-1.0.1h.tar.gz -tar xfz openssl-1.0.1h.tar.gz -cd openssl-1.0.1h -./Configure dist -make CC=/tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc AR="/tmp/my-android-toolchain/bin/arm-linux-androideabi-ar r" RANLIB=/tmp/my-android-toolchain/bin/arm-linux-androideabi-ranlib -cd - + wget http://dl.google.com/android/ndk/android-ndk-r8b-linux-x86.tar.bz2 + tar xfj android-ndk-r8b-linux-x86.tar.bz2 + ./android-ndk-r8b/build/tools/make-standalone-toolchain.sh --platform=android-5 --install-dir=/tmp/my-android-toolchain + +- Download and cross-compile OpenSSL for ARM: + + wget http://www.openssl.org/source/openssl-1.0.1c.tar.gz + tar xfz openssl-1.0.1c.tar.gz + cd openssl-1.0.1c + ./Configure dist + make CC=/tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc AR="/tmp/my-android-toolchain/bin/arm-linux-androideabi-ar r" RANLIB=/tmp/my-android-toolchain/bin/arm-linux-androideabi-ranlib - Clone and cross-compile tinc: -git clone git://tinc-vpn.org/tinc -cd tinc -autoreconf -fsi -CC=/tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc ./configure --host=arm-linux --disable-lzo --with-openssl-lib=$HOME/android/openssl-1.0.1g --with-openssl-include=$HOME/android/openssl-1.0.1g/include/ --disable-hardening -make -j5 - -- Strip tincd binary to make it smaller -/tmp/my-android-toolchain/bin/arm-linux-androideabi-strip src/tincd + git clone git://tinc-vpn.org/tinc + cd tinc + autoreconf -fsi + CC=/tmp/my-android-toolchain/bin/arm-linux-androideabi-gcc ./configure --host=arm-linux --disable-lzo --with-openssl-lib=$HOME/android/openssl-1.0.1c --with-openssl-include=$HOME/android/openssl-1.0.1c/include/ + make -j5 diff --git a/THANKS b/THANKS index a0f7966..b717b37 100644 --- a/THANKS +++ b/THANKS @@ -1,9 +1,11 @@ We would like to thank the following people for their contributions to tinc: +* Aaron Li * Alexander Reil and Gemeinde Berg * Alexander Ried * Alexis Hildebrandt * Allesandro Gatti +* Andreas Rammhold * Andreas van Cranenburgh * Andrew Hahn * Anthony G. Basile @@ -22,28 +24,32 @@ We would like to thank the following people for their contributions to tinc: * Delf Eldkraft * Dennis Joachimsthaler * dnk -* Егор Палкин * Élie Bouttier * Enrique Zanardi * Erik Tews * Etienne Dechamps +* Fabian Maurer * Florent Clairambault * Florian Forster * Florian Klink * Florian Weik * Flynn Marquardt * Franz Pletz +* Fufu Fang * Gary Kessler and Claudia Gonzalez * Grzegorz Dymarek * Gusariev Oleksandr * Hans Bayle * Harvest -* Ivo van Dong +* Huai An Hsu +* iczero +* Ilia Pavlikhin +* Ivan Mirić * Ivo Smits +* Ivo van Dong * James Cook * James MacLean * Jamie Briggs -* Jan Štembera * Jason Harper * Jason Livesay * Jasper Krijgsman @@ -51,17 +57,21 @@ We would like to thank the following people for their contributions to tinc: * Jeroen Domburg * Jeroen Ubbink * Jerome Etienne -* Jo-Philipp Wich +* Jiang Sheng * Jochen Voss +* Jo-Philipp Wich * Julien Muchembled * Lavrans Laading +* leptonyu * Loïc Dachary * Loïc Grenié * Lubomír Bulej * luckyhacky * LunarShaddow +* Maciej S. Szmigiero * Mads Kiilerich * Marc A. Lehmann +* Marco Oggioni * Mark Glines * Mark Petryk * Markus Goetz @@ -73,30 +83,33 @@ We would like to thank the following people for their contributions to tinc: * Max Rijevski * Menno Smits * Mesar Hameed -* Michael Taylor * Michael Tokarev * Michael Yonli * Miles Nordin -* Nathan Stratton Treadway * Murat Donmez +* Nathan Stratton Treadway * Nick Hibma * Nick Patavalis +* Pacien Tran-Girard +* Patrick Helms * Paul Littlefield * Philipp Babel * Pierre Emeriaud * Pierre-Olivier Mercier -* Rafael Wolf * Rafael Sadowski * Rafał Leśniak +* René Rüthlein * Rhosyn Celyn * Robert van der Meulen * Robert Waniek +* Rosen Penev * Rumko * Ryan Miller * Sam Bryan * Samuel Thibault * Saverio Proto * Scott Lamb +* Shengjing Zhu * Steffan Karger * Stig Fagrell * Sven-Haegar Koch @@ -111,7 +124,9 @@ We would like to thank the following people for their contributions to tinc: * Vil Brekin * Vincent Laurent * Vittorio Gambaletta +* Volker Augustin * Wendy Willard +* Werner Schreiber * Wessel Dankers * William A. Kennington III * William McArthur diff --git a/aclocal.m4 b/aclocal.m4 index a50e7c2..86efcb4 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.16.2 -*- Autoconf -*- +# generated automatically by aclocal 1.16.3 -*- Autoconf -*- # Copyright (C) 1996-2020 Free Software Foundation, Inc. @@ -35,7 +35,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.16' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.16.2], [], +m4_if([$1], [1.16.3], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -51,7 +51,7 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.16.2])dnl +[AM_AUTOMAKE_VERSION([1.16.3])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) @@ -703,12 +703,7 @@ AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl AC_REQUIRE_AUX_FILE([missing])dnl if test x"${MISSING+set}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; - *) - MISSING="\${SHELL} $am_aux_dir/missing" ;; - esac + MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then @@ -1140,7 +1135,12 @@ m4_include([m4/ax_append_flag.m4]) m4_include([m4/ax_cflags_warn_all.m4]) m4_include([m4/ax_check_compile_flag.m4]) m4_include([m4/ax_check_link_flag.m4]) +m4_include([m4/ax_code_coverage.m4]) m4_include([m4/ax_require_defined.m4]) +m4_include([m4/curses.m4]) +m4_include([m4/libgcrypt.m4]) m4_include([m4/lzo.m4]) +m4_include([m4/miniupnpc.m4]) m4_include([m4/openssl.m4]) +m4_include([m4/readline.m4]) m4_include([m4/zlib.m4]) diff --git a/bash_completion.d/Makefile.am b/bash_completion.d/Makefile.am new file mode 100644 index 0000000..9665dfe --- /dev/null +++ b/bash_completion.d/Makefile.am @@ -0,0 +1,2 @@ +bash_completiondir = @datarootdir@/bash-completion/completions/ +dist_bash_completion_DATA = tinc diff --git a/bash_completion.d/Makefile.in b/bash_completion.d/Makefile.in new file mode 100644 index 0000000..96d5323 --- /dev/null +++ b/bash_completion.d/Makefile.in @@ -0,0 +1,490 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = bash_completion.d +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ + $(top_srcdir)/m4/ax_append_flag.m4 \ + $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dist_bash_completion_DATA) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(bash_completiondir)" +DATA = $(dist_bash_completion_DATA) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +systemd_path = @systemd_path@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +bash_completiondir = @datarootdir@/bash-completion/completions/ +dist_bash_completion_DATA = tinc +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu bash_completion.d/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu bash_completion.d/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-dist_bash_completionDATA: $(dist_bash_completion_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_bash_completion_DATA)'; test -n "$(bash_completiondir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bash_completiondir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bash_completiondir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(bash_completiondir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(bash_completiondir)" || exit $$?; \ + done + +uninstall-dist_bash_completionDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_bash_completion_DATA)'; test -n "$(bash_completiondir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(bash_completiondir)'; $(am__uninstall_files_from_dir) +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(DATA) +installdirs: + for dir in "$(DESTDIR)$(bash_completiondir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-dist_bash_completionDATA + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-dist_bash_completionDATA + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic cscopelist-am \ + ctags-am distclean distclean-generic distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dist_bash_completionDATA install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic pdf pdf-am ps ps-am tags-am uninstall \ + uninstall-am uninstall-dist_bash_completionDATA + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/bash_completion.d/tinc b/bash_completion.d/tinc new file mode 100644 index 0000000..575f412 --- /dev/null +++ b/bash_completion.d/tinc @@ -0,0 +1,92 @@ +_tinc() { + local cur prev opts confvars commands nets + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-c -d -D -K -n -o -L -R -U --config --no-detach --debug --net --option --mlock --logfile --pidfile --chroot --user --help --version" + confvars="Address AddressFamily BindToAddress BindToInterface Broadcast BroadcastSubnet Cipher ClampMSS Compression ConnectTo DecrementTTL Device DeviceStandby DeviceType Digest DirectOnly Ed25519PrivateKeyFile Ed25519PublicKey Ed25519PublicKeyFile ExperimentalProtocol Forwarding FWMark GraphDumpFile Hostnames IffOneQueue IndirectData Interface InvitationExpire KeyExpire ListenAddress LocalDiscovery MACExpire MACLength MaxOutputBufferSize MaxTimeout Mode MTUInfoInterval Name PMTU PMTUDiscovery PingInterval PingTimeout Port PriorityInheritance PrivateKeyFile ProcessPriority Proxy PublicKeyFile ReplayWindow StrictSubnets Subnet TCPOnly TunnelServer UDPDiscovery UDPDiscoveryKeepaliveInterval UDPDiscoveryInterval UDPDiscoveryTimeout UDPInfoInterval UDPRcvBuf UDPSndBuf UPnP UPnPDiscoverWait UPnPRefreshPeriod VDEGroup VDEPort Weight" + commands="add connect debug del disconnect dump edit export export-all generate-ed25519-keys generate-keys generate-rsa-keys get help import info init invite join list log network pcap pid purge reload restart retry set sign start stop top verify version" + + case ${prev} in + -c|--config) + compopt -o dirnames 2>/dev/null + return 0 + ;; + -n|--net) + nets="" + pushd /etc/tinc >/dev/null 2>/dev/null + for dir in *; do + if [[ -f "$dir/tinc.conf" ]]; then + nets="$nets $dir" + fi + done + popd >/dev/null 2>/dev/null + COMPREPLY=( $(compgen -W "${nets}" -- ${cur}) ) + return 0 + ;; + -o|--option) + compopt -o nospace + COMPREPLY=( $(compgen -W "${confvars}" -- ${cur}) ) + if [[ ${#COMPREPLY[*]} == 1 ]] ; then + COMPREPLY=$COMPREPLY= + fi + return 0 + ;; + -U|--user) + COMPREPLY=( $(compgen -u ${cur}) ) + return 0 + ;; + --logfile|--pidfile) + compopt -o filenames 2>/dev/null + COMPREPLY=( $(compgen -f ${cur}) ) + return 0 + esac + if [[ ${cur} == -* ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + if [[ $1 == "d" ]]; then + if [[ -z ${cur} ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + fi + return 0 + fi + COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) + case $prev in + get|set|add|del) + COMPREPLY=( $(compgen -W "${confvars}" -- ${cur}) ) + return 0 + ;; + dump|list|reachable) + COMPREPLY=( $(compgen -W "reachable nodes edges subnets connections graph invitations" -- ${cur}) ) + return 0 + ;; + network) + nets="" + pushd /etc/tinc >/dev/null 2>/dev/null + for dir in *; do + if [[ -f "$dir/tinc.conf" ]]; then + nets="$nets $dir" + fi + done + popd >/dev/null 2>/dev/null + COMPREPLY=( $(compgen -W "${nets}" -- ${cur}) ) + return 0 + ;; + esac + if [[ -z ${cur} ]] ; then + COMPREPLY=( $(compgen -W "${opts} ${commands}" -- ${cur}) ) + fi + return 0 +} + +_tincd() { + _tinc d; +} + +_tincctl() { + _tinc ctl; +} + +complete -F _tincd tincd +complete -F _tincctl tinc diff --git a/config.h.in b/config.h.in index cc68348..07a8491 100644 --- a/config.h.in +++ b/config.h.in @@ -1,5 +1,8 @@ /* config.h.in. Generated from configure.ac by autoheader. */ +/* Disable support for the legacy (tinc 1.0) protocol */ +#undef DISABLE_LEGACY + /* Support for jumbograms (packets up to 9000 bytes) */ #undef ENABLE_JUMBOGRAMS @@ -15,9 +18,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_INET_H -/* Define to 1 if you have the header file. */ -#undef HAVE_ARPA_NAMESER_H - /* Define to 1 if you have the `asprintf' function. */ #undef HAVE_ASPRINTF @@ -27,8 +27,11 @@ /* Unknown BSD variant */ #undef HAVE_BSD -/* Cygwin */ -#undef HAVE_CYGWIN +/* have curses support */ +#undef HAVE_CURSES + +/* Define to 1 if you have the header file. */ +#undef HAVE_CURSES_H /* Define to 1 if you have the `daemon' function. */ #undef HAVE_DAEMON @@ -40,22 +43,6 @@ you don't. */ #undef HAVE_DECL_EVP_AES_256_CFB -/* Define to 1 if you have the declaration of `freeaddrinfo', and to 0 if you - don't. */ -#undef HAVE_DECL_FREEADDRINFO - -/* Define to 1 if you have the declaration of `gai_strerror', and to 0 if you - don't. */ -#undef HAVE_DECL_GAI_STRERROR - -/* Define to 1 if you have the declaration of `getaddrinfo', and to 0 if you - don't. */ -#undef HAVE_DECL_GETADDRINFO - -/* Define to 1 if you have the declaration of `getnameinfo', and to 0 if you - don't. */ -#undef HAVE_DECL_GETNAMEINFO - /* Define to 1 if you have the declaration of `OpenSSL_add_all_algorithms', and to 0 if you don't. */ #undef HAVE_DECL_OPENSSL_ADD_ALL_ALGORITHMS @@ -73,6 +60,9 @@ /* DragonFly */ #undef HAVE_DRAGONFLY +/* Define to 1 if you have the `ERR_remove_state' function. */ +#undef HAVE_ERR_REMOVE_STATE + /* Define to 1 if you have the `EVP_CIPHER_CTX_new' function. */ #undef HAVE_EVP_CIPHER_CTX_NEW @@ -94,6 +84,9 @@ /* FreeBSD */ #undef HAVE_FREEBSD +/* Define to 1 if you have the header file. */ +#undef HAVE_GCRYPT_H + /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H @@ -103,12 +96,12 @@ /* Define to 1 if you have the `gettimeofday' function. */ #undef HAVE_GETTIMEOFDAY +/* Define to 1 if you have the `HMAC_CTX_new' function. */ +#undef HAVE_HMAC_CTX_NEW + /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H -/* Define to 1 if you have the `nsl' library (-lnsl). */ -#undef HAVE_LIBNSL - /* Define to 1 if you have the `resolv' library (-lresolv). */ #undef HAVE_LIBRESOLV @@ -142,9 +135,18 @@ /* MinGW */ #undef HAVE_MINGW +/* have miniupnpc support */ +#undef HAVE_MINIUPNPC + +/* Define to 1 if you have the header file. */ +#undef HAVE_MINIUPNPC_MINIUPNPC_H + /* Define to 1 if you have the `mlockall' function. */ #undef HAVE_MLOCKALL +/* Define to 1 if you have the `nanosleep' function. */ +#undef HAVE_NANOSLEEP + /* NetBSD */ #undef HAVE_NETBSD @@ -232,27 +234,36 @@ /* Define to 1 if you have the header file. */ #undef HAVE_OPENSSL_SHA_H -/* Define to 1 if you have the `pselect' function. */ -#undef HAVE_PSELECT - /* Define to 1 if you have the `putenv' function. */ #undef HAVE_PUTENV /* Define to 1 if you have the `RAND_bytes' function. */ #undef HAVE_RAND_BYTES +/* have readline support */ +#undef HAVE_READLINE + +/* Define to 1 if you have the header file. */ +#undef HAVE_READLINE_HISTORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_READLINE_READLINE_H + +/* Define to 1 if you have the `recvmmsg' function. */ +#undef HAVE_RECVMMSG + /* Define to 1 if you have the header file. */ #undef HAVE_RESOLV_H /* Define to 1 if you have the `RSA_set0_key' function. */ #undef HAVE_RSA_SET0_KEY -/* Define to 1 if the system has the type `socklen_t'. */ -#undef HAVE_SOCKLEN_T - /* Solaris/SunOS */ #undef HAVE_SOLARIS +/* Define to 1 if you have the header file. */ +#undef HAVE_STDDEF_H + /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H @@ -268,9 +279,6 @@ /* Define to 1 if you have the `strsignal' function. */ #undef HAVE_STRSIGNAL -/* Define to 1 if the system has the type `struct addrinfo'. */ -#undef HAVE_STRUCT_ADDRINFO - /* Define to 1 if the system has the type `struct arphdr'. */ #undef HAVE_STRUCT_ARPHDR @@ -286,12 +294,6 @@ /* Define to 1 if the system has the type `struct icmp6_hdr'. */ #undef HAVE_STRUCT_ICMP6_HDR -/* Define to 1 if the system has the type `struct in6_addr'. */ -#undef HAVE_STRUCT_IN6_ADDR - -/* Define to 1 if the system has the type `struct in_addr'. */ -#undef HAVE_STRUCT_IN_ADDR - /* Define to 1 if the system has the type `struct ip'. */ #undef HAVE_STRUCT_IP @@ -304,15 +306,9 @@ /* Define to 1 if the system has the type `struct nd_opt_hdr'. */ #undef HAVE_STRUCT_ND_OPT_HDR -/* Define to 1 if the system has the type `struct sockaddr_in6'. */ -#undef HAVE_STRUCT_SOCKADDR_IN6 - /* Define to 1 if you have the header file. */ #undef HAVE_SYSLOG_H -/* Define to 1 if you have the `system' function. */ -#undef HAVE_SYSTEM - /* Define to 1 if you have the header file. */ #undef HAVE_SYS_FILE_H @@ -340,8 +336,8 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_UIO_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UN_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_WAIT_H @@ -352,9 +348,6 @@ /* Define to 1 if you have the `unsetenv' function. */ #undef HAVE_UNSETENV -/* Define to 1 if you have the `usleep' function. */ -#undef HAVE_USLEEP - /* Define to 1 if you have the `vsyslog' function. */ #undef HAVE_VSYSLOG @@ -388,9 +381,6 @@ /* Define to the version of this package. */ #undef PACKAGE_VERSION -/* Define as the return type of signal handlers (`int' or `void'). */ -#undef RETSIGTYPE - /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS @@ -419,9 +409,6 @@ /* Version number of package */ #undef VERSION -/* Compile with support for Windows 2000 */ -#undef WITH_WINDOWS2000 - /* Define to 1 if on MINIX. */ #undef _MINIX @@ -432,11 +419,11 @@ /* Define to 1 if you need to in order for `stat' and other things to work. */ #undef _POSIX_SOURCE -/* Enable BSD extensions */ -#undef __USE_BSD - /* Defined if the __malloc__ attribute is not supported. */ #undef __malloc__ -/* Define to `int' if does not define. */ -#undef pid_t +/* Defined if the __nonnull__ attribute is not supported. */ +#undef __nonnull__ + +/* Defined if the __warn_unused_result__ attribute is not supported. */ +#undef __warn_unused_result__ diff --git a/configure b/configure index 2f7cd24..ea50e6f 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for tinc 1.0.36. +# Generated by GNU Autoconf 2.69 for tinc 1.1pre18. # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@ -577,8 +577,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='tinc' PACKAGE_TARNAME='tinc' -PACKAGE_VERSION='1.0.36' -PACKAGE_STRING='tinc 1.0.36' +PACKAGE_VERSION='1.1pre18' +PACKAGE_STRING='tinc 1.1pre18' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -623,6 +623,15 @@ ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS +MINIUPNPC_FALSE +MINIUPNPC_TRUE +MINIUPNPC_LIBS +GCRYPT_FALSE +GCRYPT_TRUE +OPENSSL_FALSE +OPENSSL_TRUE +READLINE_LIBS +CURSES_LIBS GETOPT_FALSE GETOPT_TRUE WITH_SYSTEMD_FALSE @@ -652,6 +661,19 @@ build_os build_vendor build_cpu build +CODE_COVERAGE_RULES +CODE_COVERAGE_LDFLAGS +CODE_COVERAGE_LIBS +CODE_COVERAGE_CXXFLAGS +CODE_COVERAGE_CFLAGS +CODE_COVERAGE_CPPFLAGS +GENHTML +LCOV +GCOV +CODE_COVERAGE_ENABLED +CODE_COVERAGE_ENABLED_FALSE +CODE_COVERAGE_ENABLED_TRUE +SED EGREP GREP CPP @@ -743,12 +765,22 @@ ac_user_opts=' enable_option_checking enable_silent_rules enable_dependency_tracking +with_gcov +enable_code_coverage enable_uml enable_vde enable_tunemu -with_windows2000 with_systemd enable_hardening +enable_legacy_protocol +enable_curses +with_curses +with_curses_include +with_curses_lib +enable_readline +with_readline +with_readline_include +with_readline_lib enable_zlib with_zlib with_zlib_include @@ -757,9 +789,16 @@ enable_lzo with_lzo with_lzo_include with_lzo_lib +with_libgcrypt +with_libgcrypt_include +with_libgcrypt_lib with_openssl with_openssl_include with_openssl_lib +enable_miniupnpc +with_miniupnpc +with_miniupnpc_include +with_miniupnpc_lib enable_jumbograms ' ac_precious_vars='build_alias @@ -1321,7 +1360,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures tinc 1.0.36 to adapt to many kinds of systems. +\`configure' configures tinc 1.1pre18 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1392,7 +1431,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of tinc 1.0.36:";; + short | recursive ) echo "Configuration of tinc 1.1pre18:";; esac cat <<\_ACEOF @@ -1406,32 +1445,56 @@ Optional Features: do not reject slow dependency extractors --disable-dependency-tracking speeds up one-time build + --enable-code-coverage Whether to enable code coverage support --enable-uml enable support for User Mode Linux --enable-vde enable support for Virtual Distributed Ethernet --enable-tunemu enable support for the tunemu driver --disable-hardening disable compiler and linker hardening flags + --disable-legacy-protocol + disable support for the legacy (tinc 1.0) protocol + --disable-curses disable curses support + --disable-readline disable readline support --disable-zlib disable zlib compression support --disable-lzo disable lzo compression support + --enable-miniupnpc enable miniupnpc support --enable-jumbograms enable support for jumbograms (packets up to 9000 bytes) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --with-windows2000 compile with support for Windows 2000. This disables - support for tunneling over existing IPv6 networks. + --with-gcov=GCOV use given GCOV for coverage (GCOV=gcov). --with-systemd[=DIR] install systemd service files [to DIR if specified] + --with-curses=DIR curses base directory, or: + --with-curses-include=DIR + curses headers directory + --with-curses-lib=DIR curses library directory + --with-readline=DIR readline base directory, or: + --with-readline-include=DIR + readline headers directory + --with-readline-lib=DIR readline library directory --with-zlib=DIR zlib base directory, or: --with-zlib-include=DIR zlib headers directory --with-zlib-lib=DIR zlib library directory --with-lzo=DIR lzo base directory, or: --with-lzo-include=DIR lzo headers directory --with-lzo-lib=DIR lzo library directory + --with-libgcrypt=DIR libgcrypt base directory, or: + --with-libgcrypt-include=DIR + libgcrypt headers directory (without trailing + /libgcrypt) + --with-libgcrypt-lib=DIR + libgcrypt library directory --with-openssl=DIR LibreSSL/OpenSSL base directory, or: --with-openssl-include=DIR LibreSSL/OpenSSL headers directory (without trailing /openssl) --with-openssl-lib=DIR LibreSSL/OpenSSL library directory + --with-miniupnpc=DIR miniupnpc base directory, or: + --with-miniupnpc-include=DIR + miniupnpc headers directory + --with-miniupnpc-lib=DIR + miniupnpc library directory Some influential environment variables: CC C compiler command @@ -1509,7 +1572,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -tinc configure 1.0.36 +tinc configure 1.1pre18 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1974,7 +2037,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by tinc $as_me 1.0.36, which was +It was created by tinc $as_me 1.1pre18, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2528,12 +2591,7 @@ program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` am_aux_dir=`cd "$ac_aux_dir" && pwd` if test x"${MISSING+set}" != xset; then - case $am_aux_dir in - *\ * | *\ *) - MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; - *) - MISSING="\${SHELL} $am_aux_dir/missing" ;; - esac + MISSING="\${SHELL} '$am_aux_dir/missing'" fi # Use eval to expand $SHELL if eval "$MISSING --is-lightweight"; then @@ -2838,7 +2896,7 @@ fi # Define the identity of the package. PACKAGE='tinc' - VERSION='1.0.36' + VERSION='1.1pre18' cat >>confdefs.h <<_ACEOF @@ -2974,9 +3032,6 @@ fi AM_BACKSLASH='\' -# Enable GNU extensions. -# Define this here, not in acconfig's @TOP@ section, since definitions -# in the latter don't make it into the configure-time tests. DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" @@ -4476,11 +4531,737 @@ $as_echo "$ac_cv_safe_to_define___extensions__" >&6; } +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -$as_echo "#define __USE_BSD 1" >>confdefs.h +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5 +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + + case $ac_cv_prog_cc_stdc in #( + no) : + ac_cv_prog_cc_c99=no; ac_cv_prog_cc_c89=no ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C99" >&5 $as_echo_n "checking for $CC option to accept ISO C99... " >&6; } if ${ac_cv_prog_cc_c99+:} false; then : $as_echo_n "(cached) " >&6 @@ -4653,9 +5434,120 @@ $as_echo "unsupported" >&6; } ;; $as_echo "$ac_cv_prog_cc_c99" >&6; } ;; esac if test "x$ac_cv_prog_cc_c99" != xno; then : + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c99 +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + ac_cv_prog_cc_stdc=$ac_cv_prog_cc_c89 +else + ac_cv_prog_cc_stdc=no +fi +fi + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO Standard C" >&5 +$as_echo_n "checking for $CC option to accept ISO Standard C... " >&6; } + if ${ac_cv_prog_cc_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +fi + + case $ac_cv_prog_cc_stdc in #( + no) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; #( + '') : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; #( + *) : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_stdc" >&5 +$as_echo "$ac_cv_prog_cc_stdc" >&6; } ;; +esac ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -4797,6 +5689,448 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + + + + + # allow to override gcov location + +# Check whether --with-gcov was given. +if test "${with_gcov+set}" = set; then : + withval=$with_gcov; _AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov +else + _AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with code coverage support" >&5 +$as_echo_n "checking whether to build with code coverage support... " >&6; } + # Check whether --enable-code-coverage was given. +if test "${enable_code_coverage+set}" = set; then : + enableval=$enable_code_coverage; +else + enable_code_coverage=no +fi + + + if test x$enable_code_coverage = xyes; then + CODE_COVERAGE_ENABLED_TRUE= + CODE_COVERAGE_ENABLED_FALSE='#' +else + CODE_COVERAGE_ENABLED_TRUE='#' + CODE_COVERAGE_ENABLED_FALSE= +fi + + CODE_COVERAGE_ENABLED=$enable_code_coverage + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_code_coverage" >&5 +$as_echo "$enable_code_coverage" >&6; } + + if test "$enable_code_coverage" = "yes" ; then : + + # check for gcov + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}$_AX_CODE_COVERAGE_GCOV_PROG_WITH", so it can be a program name with args. +set dummy ${ac_tool_prefix}$_AX_CODE_COVERAGE_GCOV_PROG_WITH; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_GCOV+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$GCOV"; then + ac_cv_prog_GCOV="$GCOV" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_GCOV="${ac_tool_prefix}$_AX_CODE_COVERAGE_GCOV_PROG_WITH" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +GCOV=$ac_cv_prog_GCOV +if test -n "$GCOV"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GCOV" >&5 +$as_echo "$GCOV" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_GCOV"; then + ac_ct_GCOV=$GCOV + # Extract the first word of "$_AX_CODE_COVERAGE_GCOV_PROG_WITH", so it can be a program name with args. +set dummy $_AX_CODE_COVERAGE_GCOV_PROG_WITH; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_GCOV+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_GCOV"; then + ac_cv_prog_ac_ct_GCOV="$ac_ct_GCOV" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_GCOV="$_AX_CODE_COVERAGE_GCOV_PROG_WITH" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_GCOV=$ac_cv_prog_ac_ct_GCOV +if test -n "$ac_ct_GCOV"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_GCOV" >&5 +$as_echo "$ac_ct_GCOV" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_GCOV" = x; then + GCOV=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + GCOV=$ac_ct_GCOV + fi +else + GCOV="$ac_cv_prog_GCOV" +fi + + if test "X$GCOV" = "X:"; then : + as_fn_error $? "gcov is needed to do coverage" "$LINENO" 5 +fi + + + if test "$GCC" = "no" ; then : + + as_fn_error $? "not compiling with gcc, which is required for gcov code coverage" "$LINENO" 5 + +fi + + # Extract the first word of "lcov", so it can be a program name with args. +set dummy lcov; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LCOV+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LCOV"; then + ac_cv_prog_LCOV="$LCOV" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LCOV="lcov" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LCOV=$ac_cv_prog_LCOV +if test -n "$LCOV"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LCOV" >&5 +$as_echo "$LCOV" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + # Extract the first word of "genhtml", so it can be a program name with args. +set dummy genhtml; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_GENHTML+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$GENHTML"; then + ac_cv_prog_GENHTML="$GENHTML" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_GENHTML="genhtml" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +GENHTML=$ac_cv_prog_GENHTML +if test -n "$GENHTML"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GENHTML" >&5 +$as_echo "$GENHTML" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + if test -z "$LCOV" ; then : + + as_fn_error $? "To enable code coverage reporting you must have lcov installed" "$LINENO" 5 + +fi + + if test -z "$GENHTML" ; then : + + as_fn_error $? "Could not find genhtml from the lcov package" "$LINENO" 5 + +fi + + CODE_COVERAGE_CPPFLAGS="-DNDEBUG" + CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" + CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" + CODE_COVERAGE_LIBS="-lgcov" + CODE_COVERAGE_LDFLAGS="$CODE_COVERAGE_LIBS" + + + + + + + + CODE_COVERAGE_RULES_CHECK=' + -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check + $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture +' + CODE_COVERAGE_RULES_CAPTURE=' + $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS) + $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS) + -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp + $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS) + @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html" +' + CODE_COVERAGE_RULES_CLEAN=' +clean: code-coverage-clean +distclean: code-coverage-clean +code-coverage-clean: + -$(LCOV) --directory $(top_builddir) -z + -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY) + -find . \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete +' + +else + + CODE_COVERAGE_RULES_CHECK=' + @echo "Need to reconfigure with --enable-code-coverage" +' + CODE_COVERAGE_RULES_CAPTURE="$CODE_COVERAGE_RULES_CHECK" + CODE_COVERAGE_RULES_CLEAN='' + +fi + +CODE_COVERAGE_RULES=' +# Code coverage +# +# Optional: +# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. +# Multiple directories may be specified, separated by whitespace. +# (Default: $(top_builddir)) +# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated +# by lcov for code coverage. (Default: +# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info) +# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage +# reports to be created. (Default: +# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage) +# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, +# set to 0 to disable it and leave empty to stay with the default. +# (Default: empty) +# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov +# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) +# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov +# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) +# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov +# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the +# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) +# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov +# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) +# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering +# lcov instance. (Default: empty) +# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov +# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) +# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the +# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) +# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml +# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) +# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore +# +# The generated report will be titled using the $(PACKAGE_NAME) and +# $(PACKAGE_VERSION). In order to add the current git hash to the title, +# use the git-version-gen script, available online. + +# Optional variables +CODE_COVERAGE_DIRECTORY ?= $(top_builddir) +CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info +CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage +CODE_COVERAGE_BRANCH_COVERAGE ?= +CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ +--rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) +CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) +CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" +CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) +CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) +CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= +CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) +CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ +$(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ +--rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) +CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS) +CODE_COVERAGE_IGNORE_PATTERN ?= + +code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V)) +code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\ + $(CODE_COVERAGE_OUTPUT_FILE); +code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V)) +code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\ + $(CODE_COVERAGE_IGNORE_PATTERN); +code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V)) +code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY); +code_coverage_quiet = $(code_coverage_quiet_$(V)) +code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) +code_coverage_quiet_0 = --quiet + +# sanitizes the test-name: replaces with underscores: dashes and dots +code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1))) + +# Use recursive makes in order to ignore errors during check +check-code-coverage:'"$CODE_COVERAGE_RULES_CHECK"' + +# Capture code coverage data +code-coverage-capture: code-coverage-capture-hook'"$CODE_COVERAGE_RULES_CAPTURE"' + +# Hook rule executed before code-coverage-capture, overridable by the user +code-coverage-capture-hook: + +'"$CODE_COVERAGE_RULES_CLEAN"' + +GITIGNOREFILES ?= +GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) + +A''M_DISTCHECK_CONFIGURE_FLAGS ?= +A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage + +.PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean +' + + + + +if test "x$enable_code_coverage" = "xyes" -a "x$origcflags" = "x"; then : + CFLAGS="" +fi # Make sure we can run config.sub. @@ -4923,17 +6257,16 @@ $as_echo "#define HAVE_BSD 1" >>confdefs.h ;; *cygwin*) - cygwin=true - -$as_echo "#define HAVE_CYGWIN 1" >>confdefs.h - + as_fn_error $? "\"Cygwin is no longer supported. Use MinGW to build native Windows binaries.\"" "$LINENO" 5 ;; *mingw*) mingw=true $as_echo "#define HAVE_MINGW 1" >>confdefs.h - LIBS="$LIBS -lws2_32 -lgdi32 -lcrypt32" + LIBS="$LIBS -lws2_32 -lgdi32 -lcrypt32 -liphlpapi" + LDFLAGS="$LDFLAGS -static" + CPPFLAGS="$CPPFLAGS -DMINIUPNP_STATICLIB" ;; *) as_fn_error $? "\"Unknown operating system.\"" "$LINENO" 5 @@ -4975,6 +6308,48 @@ fi done + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + LIBS="$LIBS -ldl" +else + as_fn_error $? "VDE plug depends on libdl." "$LINENO" 5; break +fi + $as_echo "#define ENABLE_VDE 1" >>confdefs.h @@ -5009,19 +6384,6 @@ fi -# Check whether --with-windows2000 was given. -if test "${with_windows2000+set}" = set; then : - withval=$with_windows2000; if test "x$with_windows2000" = "xyes"; then : - -$as_echo "#define WITH_WINDOWS2000 1" >>confdefs.h - -fi - - -fi - - - # Check whether --with-systemd was given. if test "${with_systemd+set}" = set; then : withval=$with_systemd; systemd=true; systemd_path="$with_systemd" @@ -5201,10 +6563,10 @@ $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi rm -f confcache -if test -d /sw/include ; then +if test -d /sw/include; then : CPPFLAGS="$CPPFLAGS -I/sw/include" fi -if test -d /sw/lib ; then +if test -d /sw/lib; then : LIBS="$LIBS -L/sw/lib" fi @@ -5250,26 +6612,36 @@ $as_echo "$ac_cv_cflags_warn_all" >&6; } case ".$ac_cv_cflags_warn_all" in .ok|.ok,*) ;; .|.no|.no,*) ;; - *) if ${CFLAGS+:} false; then : - case " $CFLAGS " in - *" $ac_cv_cflags_warn_all "*) - { { $as_echo "$as_me:${as_lineno-$LINENO}: : CFLAGS already contains \$ac_cv_cflags_warn_all"; } >&5 + *) +if ${CFLAGS+:} false; then : + + case " $CFLAGS " in #( + *" $ac_cv_cflags_warn_all "*) : + { { $as_echo "$as_me:${as_lineno-$LINENO}: : CFLAGS already contains \$ac_cv_cflags_warn_all"; } >&5 (: CFLAGS already contains $ac_cv_cflags_warn_all) 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } - ;; - *) - { { $as_echo "$as_me:${as_lineno-$LINENO}: : CFLAGS=\"\$CFLAGS \$ac_cv_cflags_warn_all\""; } >&5 - (: CFLAGS="$CFLAGS $ac_cv_cflags_warn_all") 2>&5 + test $ac_status = 0; } ;; #( + *) : + + as_fn_append CFLAGS " $ac_cv_cflags_warn_all" + { { $as_echo "$as_me:${as_lineno-$LINENO}: : CFLAGS=\"\$CFLAGS\""; } >&5 + (: CFLAGS="$CFLAGS") 2>&5 ac_status=$? $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } - CFLAGS="$CFLAGS $ac_cv_cflags_warn_all" - ;; - esac + ;; +esac + else - CFLAGS="$ac_cv_cflags_warn_all" + + CFLAGS=$ac_cv_cflags_warn_all + { { $as_echo "$as_me:${as_lineno-$LINENO}: : CFLAGS=\"\$CFLAGS\""; } >&5 + (: CFLAGS="$CFLAGS") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + fi ;; esac @@ -5618,8 +6990,7 @@ fi fi; - -for ac_header in syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/wait.h netdb.h arpa/inet.h arpa/nameser.h dirent.h getopt.h +for ac_header in syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h getopt.h stddef.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -5632,7 +7003,7 @@ fi done -for ac_header in net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/if_utun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h +for ac_header in net/if.h net/if_types.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#include \"$srcdir/src/have.h\" @@ -5678,18 +7049,6 @@ fi done -ac_fn_c_check_type "$LINENO" "pid_t" "ac_cv_type_pid_t" "$ac_includes_default" -if test "x$ac_cv_type_pid_t" = xyes; then : - -else - -cat >>confdefs.h <<_ACEOF -#define pid_t int -_ACEOF - -fi - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working __malloc__ attribute" >&5 $as_echo_n "checking for working __malloc__ attribute... " >&6; } @@ -5701,8 +7060,8 @@ else CFLAGS="$CFLAGS -Wall -Werror" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -void *test(void) __attribute__ ((__malloc__)); - void *test(void) { return (void *)0; } +void *test(void *x) __attribute__ ((__malloc__)); + void *test(void *x) { return (void *)x; } _ACEOF if ac_fn_c_try_compile "$LINENO"; then : @@ -5725,17 +7084,74 @@ $as_echo "#define __malloc__ /**/" >>confdefs.h fi -ac_fn_c_check_type "$LINENO" "socklen_t" "ac_cv_type_socklen_t" "#include \"$srcdir/src/have.h\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working __nonnull__ attribute" >&5 +$as_echo_n "checking for working __nonnull__ attribute... " >&6; } +if ${tinc_cv_attribute___nonnull__+:} false; then : + $as_echo_n "(cached) " >&6 +else -" -if test "x$ac_cv_type_socklen_t" = xyes; then : + tempcflags="$CFLAGS" + CFLAGS="$CFLAGS -Wall -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +void *test(void *x) __attribute__ ((__nonnull__)); + void *test(void *x) { return (void *)x; } -cat >>confdefs.h <<_ACEOF -#define HAVE_SOCKLEN_T 1 _ACEOF - +if ac_fn_c_try_compile "$LINENO"; then : + tinc_cv_attribute___nonnull__=yes +else + tinc_cv_attribute___nonnull__=no fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CFLAGS="$tempcflags" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tinc_cv_attribute___nonnull__" >&5 +$as_echo "$tinc_cv_attribute___nonnull__" >&6; } + + if test ${tinc_cv_attribute___nonnull__} = no; then + +$as_echo "#define __nonnull__ /**/" >>confdefs.h + + fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for working __warn_unused_result__ attribute" >&5 +$as_echo_n "checking for working __warn_unused_result__ attribute... " >&6; } +if ${tinc_cv_attribute___warn_unused_result__+:} false; then : + $as_echo_n "(cached) " >&6 +else + + tempcflags="$CFLAGS" + CFLAGS="$CFLAGS -Wall -Werror" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +void *test(void *x) __attribute__ ((__warn_unused_result__)); + void *test(void *x) { return (void *)x; } + +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + tinc_cv_attribute___warn_unused_result__=yes +else + tinc_cv_attribute___warn_unused_result__=no + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CFLAGS="$tempcflags" + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $tinc_cv_attribute___warn_unused_result__" >&5 +$as_echo "$tinc_cv_attribute___warn_unused_result__" >&6; } + + if test ${tinc_cv_attribute___warn_unused_result__} = no; then + +$as_echo "#define __warn_unused_result__ /**/" >>confdefs.h + + fi + + ac_fn_c_check_type "$LINENO" "struct ether_header" "ac_cv_type_struct_ether_header" "#include \"$srcdir/src/have.h\" " @@ -5768,28 +7184,6 @@ cat >>confdefs.h <<_ACEOF _ACEOF -fi -ac_fn_c_check_type "$LINENO" "struct in_addr" "ac_cv_type_struct_in_addr" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_type_struct_in_addr" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE_STRUCT_IN_ADDR 1 -_ACEOF - - -fi -ac_fn_c_check_type "$LINENO" "struct addrinfo" "ac_cv_type_struct_addrinfo" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_type_struct_addrinfo" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE_STRUCT_ADDRINFO 1 -_ACEOF - - fi ac_fn_c_check_type "$LINENO" "struct ip" "ac_cv_type_struct_ip" "#include \"$srcdir/src/have.h\" @@ -5812,28 +7206,6 @@ cat >>confdefs.h <<_ACEOF _ACEOF -fi -ac_fn_c_check_type "$LINENO" "struct in6_addr" "ac_cv_type_struct_in6_addr" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_type_struct_in6_addr" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE_STRUCT_IN6_ADDR 1 -_ACEOF - - -fi -ac_fn_c_check_type "$LINENO" "struct sockaddr_in6" "ac_cv_type_struct_sockaddr_in6" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_type_struct_sockaddr_in6" = xyes; then : - -cat >>confdefs.h <<_ACEOF -#define HAVE_STRUCT_SOCKADDR_IN6 1 -_ACEOF - - fi ac_fn_c_check_type "$LINENO" "struct ip6_hdr" "ac_cv_type_struct_ip6_hdr" "#include \"$srcdir/src/have.h\" @@ -5881,40 +7253,7 @@ _ACEOF fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking return type of signal handlers" >&5 -$as_echo_n "checking return type of signal handlers... " >&6; } -if ${ac_cv_type_signal+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -#include - -int -main () -{ -return *(signal (0, 0)) (0) == 1; - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - ac_cv_type_signal=int -else - ac_cv_type_signal=void -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_type_signal" >&5 -$as_echo "$ac_cv_type_signal" >&6; } - -cat >>confdefs.h <<_ACEOF -#define RETSIGTYPE $ac_cv_type_signal -_ACEOF - - -for ac_func in asprintf daemon fchmod flock fork gettimeofday mlockall pselect putenv strsignal system unsetenv usleep vsyslog devname fdevname +for ac_func in asprintf daemon fchmod flock fork gettimeofday mlockall putenv recvmmsg strsignal nanosleep unsetenv vsyslog devname fdevname do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -5945,164 +7284,6 @@ else fi - -ac_fn_c_check_func "$LINENO" "socket" "ac_cv_func_socket" -if test "x$ac_cv_func_socket" = xyes; then : - -else - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5 -$as_echo_n "checking for connect in -lsocket... " >&6; } -if ${ac_cv_lib_socket_connect+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-lsocket $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char connect (); -int -main () -{ -return connect (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_socket_connect=yes -else - ac_cv_lib_socket_connect=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 -$as_echo "$ac_cv_lib_socket_connect" >&6; } -if test "x$ac_cv_lib_socket_connect" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_LIBSOCKET 1 -_ACEOF - - LIBS="-lsocket $LIBS" - -fi - - -fi - -ac_fn_c_check_func "$LINENO" "gethostbyname" "ac_cv_func_gethostbyname" -if test "x$ac_cv_func_gethostbyname" = xyes; then : - -else - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gethostbyname in -lnsl" >&5 -$as_echo_n "checking for gethostbyname in -lnsl... " >&6; } -if ${ac_cv_lib_nsl_gethostbyname+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-lnsl $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char gethostbyname (); -int -main () -{ -return gethostbyname (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_nsl_gethostbyname=yes -else - ac_cv_lib_nsl_gethostbyname=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nsl_gethostbyname" >&5 -$as_echo "$ac_cv_lib_nsl_gethostbyname" >&6; } -if test "x$ac_cv_lib_nsl_gethostbyname" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_LIBNSL 1 -_ACEOF - - LIBS="-lnsl $LIBS" - -fi - - -fi - - -ac_fn_c_check_decl "$LINENO" "freeaddrinfo" "ac_cv_have_decl_freeaddrinfo" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_have_decl_freeaddrinfo" = xyes; then : - ac_have_decl=1 -else - ac_have_decl=0 -fi - -cat >>confdefs.h <<_ACEOF -#define HAVE_DECL_FREEADDRINFO $ac_have_decl -_ACEOF -ac_fn_c_check_decl "$LINENO" "gai_strerror" "ac_cv_have_decl_gai_strerror" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_have_decl_gai_strerror" = xyes; then : - ac_have_decl=1 -else - ac_have_decl=0 -fi - -cat >>confdefs.h <<_ACEOF -#define HAVE_DECL_GAI_STRERROR $ac_have_decl -_ACEOF -ac_fn_c_check_decl "$LINENO" "getaddrinfo" "ac_cv_have_decl_getaddrinfo" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_have_decl_getaddrinfo" = xyes; then : - ac_have_decl=1 -else - ac_have_decl=0 -fi - -cat >>confdefs.h <<_ACEOF -#define HAVE_DECL_GETADDRINFO $ac_have_decl -_ACEOF -ac_fn_c_check_decl "$LINENO" "getnameinfo" "ac_cv_have_decl_getnameinfo" "#include \"$srcdir/src/have.h\" - -" -if test "x$ac_cv_have_decl_getnameinfo" = xyes; then : - ac_have_decl=1 -else - ac_have_decl=0 -fi - -cat >>confdefs.h <<_ACEOF -#define HAVE_DECL_GETNAMEINFO $ac_have_decl -_ACEOF - - ac_fn_c_check_decl "$LINENO" "res_init" "ac_cv_have_decl_res_init" " #include #include @@ -6166,6 +7347,99 @@ fi fi +case $host_os in + *linux*) + for ac_header in linux/if_tun.h +do : + ac_fn_c_check_header_compile "$LINENO" "linux/if_tun.h" "ac_cv_header_linux_if_tun_h" "#include \"$srcdir/src/have.h\" + +" +if test "x$ac_cv_header_linux_if_tun_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LINUX_IF_TUN_H 1 +_ACEOF + +else + as_fn_error $? "Required header file missng" "$LINENO" 5 +fi + +done + + ;; + *bsd*|*dragonfly*|*darwin*) + for ac_header in net/if_tun.h net/if_utun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#include \"$srcdir/src/have.h\" + +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + ;; + *solaris*) + ac_fn_c_check_func "$LINENO" "socket" "ac_cv_func_socket" +if test "x$ac_cv_func_socket" = xyes; then : + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for connect in -lsocket" >&5 +$as_echo_n "checking for connect in -lsocket... " >&6; } +if ${ac_cv_lib_socket_connect+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsocket $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char connect (); +int +main () +{ +return connect (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_socket_connect=yes +else + ac_cv_lib_socket_connect=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_socket_connect" >&5 +$as_echo "$ac_cv_lib_socket_connect" >&6; } +if test "x$ac_cv_lib_socket_connect" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBSOCKET 1 +_ACEOF + + LIBS="-lsocket $LIBS" + +fi + +fi + + ;; + *) + ;; +esac + cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure @@ -6252,6 +7526,309 @@ $as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi rm -f confcache +# Check whether --enable-legacy-protocol was given. +if test "${enable_legacy_protocol+set}" = set; then : + enableval=$enable_legacy_protocol; if test "x$enable_legacy_protocol" = "xno"; then : + +$as_echo "#define DISABLE_LEGACY 1" >>confdefs.h + +fi + + +fi + + + + + + # Check whether --enable-curses was given. +if test "${enable_curses+set}" = set; then : + enableval=$enable_curses; +fi + + if test "x$enable_curses" != "xno"; then : + + +$as_echo "#define HAVE_CURSES 1" >>confdefs.h + + curses=true + +# Check whether --with-curses was given. +if test "${with_curses+set}" = set; then : + withval=$with_curses; curses="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + +fi + + + +# Check whether --with-curses-include was given. +if test "${with_curses_include+set}" = set; then : + withval=$with_curses_include; curses_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval" + +fi + + + +# Check whether --with-curses-lib was given. +if test "${with_curses_lib+set}" = set; then : + withval=$with_curses_lib; curses_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval" + +fi + + + for ac_header in curses.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "curses.h" "ac_cv_header_curses_h" "$ac_includes_default" +if test "x$ac_cv_header_curses_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_CURSES_H 1 +_ACEOF + +else + as_fn_error $? "\"curses header files not found.\"" "$LINENO" 5; break + +fi + +done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for initscr in -lncurses" >&5 +$as_echo_n "checking for initscr in -lncurses... " >&6; } +if ${ac_cv_lib_ncurses_initscr+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lncurses $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char initscr (); +int +main () +{ +return initscr (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_ncurses_initscr=yes +else + ac_cv_lib_ncurses_initscr=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ncurses_initscr" >&5 +$as_echo "$ac_cv_lib_ncurses_initscr" >&6; } +if test "x$ac_cv_lib_ncurses_initscr" = xyes; then : + CURSES_LIBS="-lncurses"; { $as_echo "$as_me:${as_lineno-$LINENO}: checking for wtimeout in -ltinfo" >&5 +$as_echo_n "checking for wtimeout in -ltinfo... " >&6; } +if ${ac_cv_lib_tinfo_wtimeout+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ltinfo $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char wtimeout (); +int +main () +{ +return wtimeout (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_tinfo_wtimeout=yes +else + ac_cv_lib_tinfo_wtimeout=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tinfo_wtimeout" >&5 +$as_echo "$ac_cv_lib_tinfo_wtimeout" >&6; } +if test "x$ac_cv_lib_tinfo_wtimeout" = xyes; then : + CURSES_LIBS+=" -ltinfo" +fi + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for initscr in -lcurses" >&5 +$as_echo_n "checking for initscr in -lcurses... " >&6; } +if ${ac_cv_lib_curses_initscr+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lcurses $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char initscr (); +int +main () +{ +return initscr (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_curses_initscr=yes +else + ac_cv_lib_curses_initscr=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_curses_initscr" >&5 +$as_echo "$ac_cv_lib_curses_initscr" >&6; } +if test "x$ac_cv_lib_curses_initscr" = xyes; then : + CURSES_LIBS="-lcurses" +else + as_fn_error $? "\"curses libraries not found.\"" "$LINENO" 5 + +fi + + +fi + + +fi + + + + + # Check whether --enable-readline was given. +if test "${enable_readline+set}" = set; then : + enableval=$enable_readline; +fi + + if test "x$enable_readline" != "xno"; then : + + +$as_echo "#define HAVE_READLINE 1" >>confdefs.h + + readline=true + +# Check whether --with-readline was given. +if test "${with_readline+set}" = set; then : + withval=$with_readline; readline="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + +fi + + + +# Check whether --with-readline-include was given. +if test "${with_readline_include+set}" = set; then : + withval=$with_readline_include; readline_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval" + +fi + + + +# Check whether --with-readline-lib was given. +if test "${with_readline_lib+set}" = set; then : + withval=$with_readline_lib; readline_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval" + +fi + + + for ac_header in readline/readline.h readline/history.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +else + as_fn_error $? "\"readline header files not found.\"" "$LINENO" 5; break + +fi + +done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for readline in -lreadline" >&5 +$as_echo_n "checking for readline in -lreadline... " >&6; } +if ${ac_cv_lib_readline_readline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lreadline $CURSES_LIBS + $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char readline (); +int +main () +{ +return readline (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_readline_readline=yes +else + ac_cv_lib_readline_readline=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_readline" >&5 +$as_echo "$ac_cv_lib_readline_readline" >&6; } +if test "x$ac_cv_lib_readline_readline" = xyes; then : + READLINE_LIBS="-lreadline" +else + as_fn_error $? "\"readline library not found.\"" "$LINENO" 5 +fi + + +fi + + # Check whether --enable-zlib was given. @@ -6532,6 +8109,99 @@ done fi +if test "x$enable_legacy_protocol" != "xno"; then : + if test -n "$with_libgcrypt"; then : + gcrypt=true; + +# Check whether --with-libgcrypt was given. +if test "${with_libgcrypt+set}" = set; then : + withval=$with_libgcrypt; libgcrypt="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + +fi + + + +# Check whether --with-libgcrypt-include was given. +if test "${with_libgcrypt_include+set}" = set; then : + withval=$with_libgcrypt_include; libgcrypt_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval" + +fi + + + +# Check whether --with-libgcrypt-lib was given. +if test "${with_libgcrypt_lib+set}" = set; then : + withval=$with_libgcrypt_lib; libgcrypt_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval" + +fi + + + for ac_header in gcrypt.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "gcrypt.h" "ac_cv_header_gcrypt_h" "$ac_includes_default" +if test "x$ac_cv_header_gcrypt_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_GCRYPT_H 1 +_ACEOF + +else + as_fn_error $? "libgcrypt header files not found." "$LINENO" 5; break + +fi + +done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gcry_cipher_encrypt in -lgcrypt" >&5 +$as_echo_n "checking for gcry_cipher_encrypt in -lgcrypt... " >&6; } +if ${ac_cv_lib_gcrypt_gcry_cipher_encrypt+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lgcrypt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char gcry_cipher_encrypt (); +int +main () +{ +return gcry_cipher_encrypt (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_gcrypt_gcry_cipher_encrypt=yes +else + ac_cv_lib_gcrypt_gcry_cipher_encrypt=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gcrypt_gcry_cipher_encrypt" >&5 +$as_echo "$ac_cv_lib_gcrypt_gcry_cipher_encrypt" >&6; } +if test "x$ac_cv_lib_gcrypt_gcry_cipher_encrypt" = xyes; then : + LIBS="-lgcrypt $LIBS" +else + as_fn_error $? "libgcrypt libraries not found." "$LINENO" 5 + +fi + + +else + openssl=true; case $host_os in *mingw*) ;; @@ -6729,7 +8399,7 @@ else fi - for ac_func in BN_GENCB_new RSA_set0_key + for ac_func in BN_GENCB_new ERR_remove_state RSA_set0_key do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -6741,6 +8411,150 @@ _ACEOF fi done + for ac_func in HMAC_CTX_new +do : + ac_fn_c_check_func "$LINENO" "HMAC_CTX_new" "ac_cv_func_HMAC_CTX_new" +if test "x$ac_cv_func_HMAC_CTX_new" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_HMAC_CTX_NEW 1 +_ACEOF + +fi +done + + +fi + + +fi + + if test -n "$openssl"; then + OPENSSL_TRUE= + OPENSSL_FALSE='#' +else + OPENSSL_TRUE='#' + OPENSSL_FALSE= +fi + + if test -n "$gcrypt"; then + GCRYPT_TRUE= + GCRYPT_FALSE='#' +else + GCRYPT_TRUE='#' + GCRYPT_FALSE= +fi + + + + # Check whether --enable-miniupnpc was given. +if test "${enable_miniupnpc+set}" = set; then : + enableval=$enable_miniupnpc; +fi + + if test "x$enable_miniupnpc" = "xyes"; then : + + +$as_echo "#define HAVE_MINIUPNPC 1" >>confdefs.h + + +# Check whether --with-miniupnpc was given. +if test "${with_miniupnpc+set}" = set; then : + withval=$with_miniupnpc; miniupnpc="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + +fi + + + +# Check whether --with-miniupnpc-include was given. +if test "${with_miniupnpc_include+set}" = set; then : + withval=$with_miniupnpc_include; miniupnpc_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval" + +fi + + + +# Check whether --with-miniupnpc-lib was given. +if test "${with_miniupnpc_lib+set}" = set; then : + withval=$with_miniupnpc_lib; miniupnpc_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval" + +fi + + + for ac_header in miniupnpc/miniupnpc.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "miniupnpc/miniupnpc.h" "ac_cv_header_miniupnpc_miniupnpc_h" "$ac_includes_default" +if test "x$ac_cv_header_miniupnpc_miniupnpc_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_MINIUPNPC_MINIUPNPC_H 1 +_ACEOF + +else + as_fn_error $? "\"miniupnpc header files not found.\"" "$LINENO" 5; break + +fi + +done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for upnpDiscover in -lminiupnpc" >&5 +$as_echo_n "checking for upnpDiscover in -lminiupnpc... " >&6; } +if ${ac_cv_lib_miniupnpc_upnpDiscover+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lminiupnpc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char upnpDiscover (); +int +main () +{ +return upnpDiscover (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_miniupnpc_upnpDiscover=yes +else + ac_cv_lib_miniupnpc_upnpDiscover=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_miniupnpc_upnpDiscover" >&5 +$as_echo "$ac_cv_lib_miniupnpc_upnpDiscover" >&6; } +if test "x$ac_cv_lib_miniupnpc_upnpDiscover" = xyes; then : + MINIUPNPC_LIBS="$LIBS -lminiupnpc" +else + as_fn_error $? "\"miniupnpc libraries not found.\"" "$LINENO" 5 + +fi + + +fi + + + + if test "x$enable_miniupnpc" = "xyes"; then + MINIUPNPC_TRUE= + MINIUPNPC_FALSE='#' +else + MINIUPNPC_TRUE='#' + MINIUPNPC_FALSE= +fi # Check whether --enable-jumbograms was given. @@ -6760,7 +8574,7 @@ if test "x$runstatedir" = "x"; then fi -ac_config_files="$ac_config_files Makefile src/Makefile doc/Makefile systemd/Makefile" +ac_config_files="$ac_config_files Makefile src/Makefile doc/Makefile test/Makefile test/testlib.sh systemd/Makefile bash_completion.d/Makefile" cat >confcache <<\_ACEOF @@ -6896,6 +8710,14 @@ if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then as_fn_error $? "conditional \"am__fastdepCC\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${CODE_COVERAGE_ENABLED_TRUE}" && test -z "${CODE_COVERAGE_ENABLED_FALSE}"; then + as_fn_error $? "conditional \"CODE_COVERAGE_ENABLED\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${LINUX_TRUE}" && test -z "${LINUX_FALSE}"; then as_fn_error $? "conditional \"LINUX\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -6936,6 +8758,18 @@ if test -z "${GETOPT_TRUE}" && test -z "${GETOPT_FALSE}"; then as_fn_error $? "conditional \"GETOPT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${OPENSSL_TRUE}" && test -z "${OPENSSL_FALSE}"; then + as_fn_error $? "conditional \"OPENSSL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${GCRYPT_TRUE}" && test -z "${GCRYPT_FALSE}"; then + as_fn_error $? "conditional \"GCRYPT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MINIUPNPC_TRUE}" && test -z "${MINIUPNPC_FALSE}"; then + as_fn_error $? "conditional \"MINIUPNPC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 @@ -7333,7 +9167,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by tinc $as_me 1.0.36, which was +This file was extended by tinc $as_me 1.1pre18, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -7399,7 +9233,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -tinc config.status 1.0.36 +tinc config.status 1.1pre18 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -7533,7 +9367,10 @@ do "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;; + "test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;; + "test/testlib.sh") CONFIG_FILES="$CONFIG_FILES test/testlib.sh" ;; "systemd/Makefile") CONFIG_FILES="$CONFIG_FILES systemd/Makefile" ;; + "bash_completion.d/Makefile") CONFIG_FILES="$CONFIG_FILES bash_completion.d/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac diff --git a/configure.ac b/configure.ac index ca2948a..6d850ed 100644 --- a/configure.ac +++ b/configure.ac @@ -1,26 +1,28 @@ dnl Process this file with autoconf to produce a configure script. -AC_PREREQ(2.61) -AC_INIT([tinc], [1.0.36]) +origcflags="$CFLAGS" + +AC_PREREQ(2.69) +AC_INIT([tinc], m4_esyscmd_s((git describe || echo UNKNOWN) | sed 's/release-//')) AC_CONFIG_SRCDIR([src/tincd.c]) -AM_INIT_AUTOMAKE([1.11 check-news std-options subdir-objects nostdinc silent-rules -Wall]) +AM_INIT_AUTOMAKE([std-options subdir-objects nostdinc silent-rules -Wall]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) AM_SILENT_RULES([yes]) -# Enable GNU extensions. -# Define this here, not in acconfig's @TOP@ section, since definitions -# in the latter don't make it into the configure-time tests. -AC_GNU_SOURCE -AC_DEFINE([__USE_BSD], 1, [Enable BSD extensions]) +AC_USE_SYSTEM_EXTENSIONS dnl Checks for programs. -AC_PROG_CC_C99 +AC_PROG_CC +AC_PROG_CC_STDC AC_PROG_CPP AC_PROG_INSTALL - AM_PROG_CC_C_O +dnl Check whether to enable code coverage testing, and if so, clear the default CFLAGS. +AX_CODE_COVERAGE +AS_IF([test "x$enable_code_coverage" = "xyes" -a "x$origcflags" = "x"], [CFLAGS=""]) + dnl Check and set OS AC_CANONICAL_HOST @@ -60,13 +62,14 @@ case $host_os in AC_DEFINE(HAVE_BSD, 1, [Unknown BSD variant]) ;; *cygwin*) - cygwin=true - AC_DEFINE(HAVE_CYGWIN, 1, [Cygwin]) + AC_MSG_ERROR("Cygwin is no longer supported. Use MinGW to build native Windows binaries.") ;; *mingw*) mingw=true AC_DEFINE(HAVE_MINGW, 1, [MinGW]) - LIBS="$LIBS -lws2_32 -lgdi32 -lcrypt32" + LIBS="$LIBS -lws2_32 -lgdi32 -lcrypt32 -liphlpapi" + LDFLAGS="$LDFLAGS -static" + CPPFLAGS="$CPPFLAGS -DMINIUPNP_STATICLIB" ;; *) AC_MSG_ERROR("Unknown operating system.") @@ -88,6 +91,7 @@ AC_ARG_ENABLE(vde, AS_HELP_STRING([--enable-vde], [enable support for Virtual Distributed Ethernet]), [ AS_IF([test "x$enable_vde" = "xyes"], [ AC_CHECK_HEADERS(libvdeplug_dyn.h, [], [AC_MSG_ERROR([VDE plug header files not found.]); break]) + AC_CHECK_LIB(dl, dlopen, [LIBS="$LIBS -ldl"], [AC_MSG_ERROR([VDE plug depends on libdl.]); break]) AC_DEFINE(ENABLE_VDE, 1, [Support for VDE]) vde=true ], @@ -107,13 +111,6 @@ AC_ARG_ENABLE(tunemu, [tunemu=false] ) -AC_ARG_WITH(windows2000, - AS_HELP_STRING([--with-windows2000], [compile with support for Windows 2000. This disables support for tunneling over existing IPv6 networks.]), - [ AS_IF([test "x$with_windows2000" = "xyes"], - [AC_DEFINE(WITH_WINDOWS2000, 1, [Compile with support for Windows 2000])]) - ] -) - AC_ARG_WITH(systemd, AS_HELP_STRING([--with-systemd@<:@=DIR@:>@], [install systemd service files @<:@to DIR if specified@:>@]), [ systemd=true; systemd_path="$with_systemd" ], @@ -137,12 +134,8 @@ AM_CONDITIONAL(WITH_SYSTEMD, test "$systemd" = true) AC_CACHE_SAVE -if test -d /sw/include ; then - CPPFLAGS="$CPPFLAGS -I/sw/include" -fi -if test -d /sw/lib ; then - LIBS="$LIBS -L/sw/lib" -fi +AS_IF([test -d /sw/include], [CPPFLAGS="$CPPFLAGS -I/sw/include"]) +AS_IF([test -d /sw/lib], [LIBS="$LIBS -L/sw/lib"]) dnl Compiler hardening flags dnl No -fstack-protector-all because it doesn't work on all platforms or architectures. @@ -169,13 +162,11 @@ AS_IF([test "x$enable_hardening" != "xno"], ] ); -dnl Checks for libraries. - dnl Checks for header files. dnl We do this in multiple stages, because unlike Linux all the other operating systems really suck and don't include their own dependencies. -AC_CHECK_HEADERS([syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/wait.h netdb.h arpa/inet.h arpa/nameser.h dirent.h getopt.h]) -AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/if_utun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h], +AC_CHECK_HEADERS([syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h getopt.h stddef.h]) +AC_CHECK_HEADERS([net/if.h net/if_types.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h], [], [], [#include "$srcdir/src/have.h"] ) AC_CHECK_HEADERS([netinet/if_ether.h netinet/ip.h netinet/ip6.h resolv.h], @@ -186,48 +177,77 @@ AC_CHECK_HEADERS([netinet/tcp.h netinet/ip_icmp.h netinet/icmp6.h], ) dnl Checks for typedefs, structures, and compiler characteristics. -AC_TYPE_PID_T - tinc_ATTRIBUTE(__malloc__) +tinc_ATTRIBUTE(__nonnull__) +tinc_ATTRIBUTE(__warn_unused_result__) -AC_CHECK_TYPES([socklen_t, struct ether_header, struct arphdr, struct ether_arp, struct in_addr, struct addrinfo, struct ip, struct icmp, struct in6_addr, struct sockaddr_in6, struct ip6_hdr, struct icmp6_hdr, struct nd_neighbor_solicit, struct nd_opt_hdr], , , +AC_CHECK_TYPES([struct ether_header, struct arphdr, struct ether_arp, struct ip, struct icmp, struct ip6_hdr, struct icmp6_hdr, struct nd_neighbor_solicit, struct nd_opt_hdr], , , [#include "$srcdir/src/have.h"] ) dnl Checks for library functions. -AC_TYPE_SIGNAL -AC_CHECK_FUNCS([asprintf daemon fchmod flock fork gettimeofday mlockall pselect putenv strsignal system unsetenv usleep vsyslog devname fdevname], +AC_CHECK_FUNCS([asprintf daemon fchmod flock fork gettimeofday mlockall putenv recvmmsg strsignal nanosleep unsetenv vsyslog devname fdevname], [], [], [#include "$srcdir/src/have.h"] ) AC_CHECK_FUNC(getopt_long, [getopt=true; AC_DEFINE(HAVE_GETOPT_LONG, 1, [getopt_long()])], [getopt=false]) AM_CONDITIONAL(GETOPT, test "$getopt" = true) -dnl Support for SunOS - -AC_CHECK_FUNC(socket, [], [ - AC_CHECK_LIB(socket, connect) -]) -AC_CHECK_FUNC(gethostbyname, [], [ - AC_CHECK_LIB(nsl, gethostbyname) -]) - -AC_CHECK_DECLS([freeaddrinfo, gai_strerror, getaddrinfo, getnameinfo], - [], [], [#include "$srcdir/src/have.h"] -) - AC_CHECK_DECLS([res_init], [AC_CHECK_LIB(resolv, res_init)], [], [ #include #include ]) +dnl Operating system specific checks +case $host_os in + *linux*) + AC_CHECK_HEADERS([linux/if_tun.h], + [], [AC_MSG_ERROR([Required header file missng])], [#include "$srcdir/src/have.h"] + ) + ;; + *bsd*|*dragonfly*|*darwin*) + AC_CHECK_HEADERS([net/if_tun.h net/if_utun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h], + [], [], [#include "$srcdir/src/have.h"] + ) + ;; + *solaris*) + AC_CHECK_FUNC(socket, [], [AC_CHECK_LIB(socket, connect)]) + ;; + *) + ;; +esac + AC_CACHE_SAVE +AC_ARG_ENABLE(legacy-protocol, + AS_HELP_STRING([--disable-legacy-protocol], [disable support for the legacy (tinc 1.0) protocol]), + [ AS_IF([test "x$enable_legacy_protocol" = "xno"], + [ AC_DEFINE(DISABLE_LEGACY, 1, [Disable support for the legacy (tinc 1.0) protocol]) ]) + ] +) + dnl These are defined in files in m4/ +dnl AC_ARG_WITH(libgcrypt, AC_HELP_STRING([--with-libgcrypt], [enable use of libgcrypt instead of OpenSSL])], []) +dnl AC_ARG_WITH(openssl, AC_HELP_STRING([--without-openssl], [disable support for OpenSSL])], []) + +tinc_CURSES +tinc_READLINE tinc_ZLIB tinc_LZO -tinc_OPENSSL + +AS_IF([test "x$enable_legacy_protocol" != "xno"], + [AS_IF([test -n "$with_libgcrypt"], + [gcrypt=true; tinc_LIBGCRYPT], + [openssl=true; tinc_OPENSSL]) + ] +) + +AM_CONDITIONAL(OPENSSL, test -n "$openssl") +AM_CONDITIONAL(GCRYPT, test -n "$gcrypt") + +tinc_MINIUPNPC +AM_CONDITIONAL(MINIUPNPC, test "x$enable_miniupnpc" = "xyes") dnl Check if support for jumbograms is requested AC_ARG_ENABLE(jumbograms, @@ -242,6 +262,6 @@ if test "x$runstatedir" = "x"; then AC_SUBST([runstatedir], ['${localstatedir}/run']) fi -AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile systemd/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile test/Makefile test/testlib.sh systemd/Makefile bash_completion.d/Makefile]) AC_OUTPUT diff --git a/debian/NEWS b/debian/NEWS index b3eb367..903f27d 100644 --- a/debian/NEWS +++ b/debian/NEWS @@ -1,14 +1,43 @@ -tinc (1.0.27-1) unstable; urgency=medium +tinc (1.1~pre11-1) experimental; urgency=medium - This package now provides native systemd service files, allowing multiple + This package now provides a native systemd service file, allowing multiple instances of tinc to be managed. Existing networks listed in /etc/tinc/nets.boot will be converted to service instances once during this upgrade. Afterwards, you can enable and disable networks using: - + systemctl enable tinc@ systemctl disable tinc@ - + If you do not have systemd installed, the SysV init script will continue to work as usual. For more information, see README.Debian. - -- Guus Sliepen Sun, 10 Apr 2016 01:33:55 +0200 + Please note that tinc 1.1pre11 is backwards compatible with tinc 1.0.x, but + is not backwards compatible with 1.1pre1 to 1.1pre10 nodes if + ExperimentalProtocol is enabled, which is the default. + + If you have more than one node running an 1.1 prerelease version in your VPN, + make sure you upgrade them all at the same time, or disable the new protocol + by adding the following line to tinc.conf: + + ExperimentalProtocol = no + + If you do want to use the new protocol, be aware that this version of tinc + switched to Ed25519 keys. You can generate a new Ed25519 keypair by running + the following command: + + tinc -n generate-ed25519-keys + + You have to manually restart tinc after this upgrade. + + -- Guus Sliepen Sat, 08 Jan 2015 14:02:27 +0100 + +tinc (1.1~pre2-1) experimental; urgency=low + + tinc-1.1 has separate control utility, tinc (without the d), which is now + used to start/stop tinc instances, to reload configuration, to get various + information about running tincd (including dump of nodes and connections) + and so on. tincd still reacts to some signals as before, but this usage is + deprecated. In particular, -k option is now gone. Also, node/connection/etc + dumps are produced on tincctl stdout, not into syslog. + + -- Michael Tokarev Sun, 07 Aug 2011 13:16:17 +0400 diff --git a/debian/README.Debian b/debian/README.Debian index d911295..e2f9da1 100644 --- a/debian/README.Debian +++ b/debian/README.Debian @@ -9,7 +9,7 @@ There are several ways in which tinc may be automatically started at boot: Systemd ------- -Since 1.0.27-1, the tinc package comes with native systemd service files. +Since 1.1~pre11-1, the tinc package comes with native systemd service files. To enable and start a net, call: systemctl enable tinc@ @@ -57,7 +57,6 @@ tinc-config tinc-debug tinc-mlock yes tinc-logfile -tinc-pidfile tinc-chroot yes tinc-user @@ -70,11 +69,10 @@ iface vpn inet static tinc-debug 1 tinc-mlock yes tinc-user nobody - tinc-pidfile /tmp/tinc.pid This will start a tinc daemon that reads its configuration from /etc/tinc/myvpn, logs at debug level 1, locks itself in RAM, runs as user nobody, and creates a network interface called "vpn". Ifup then sets the address and netmask on that interface. - -- Guus Sliepen , Sun, 10 April 2016, 01:38:08 +0200 + -- Guus Sliepen , Thu, 8 January 2015, 13:37:46 +0100 diff --git a/debian/changelog b/debian/changelog index 04508d0..95d78d6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,163 +1,88 @@ -tinc (1.0.36-2) unstable; urgency=medium +tinc (1.1~pre18-1) experimental; urgency=medium - * Disable support for libvdeplug. Closes: #973233 + * New upstream release. + - The patch for EVP_DecryptUpdate is no longer necessary. + * Disable support for VDE. - -- Guus Sliepen Sun, 22 Nov 2020 10:40:27 +0100 + -- Guus Sliepen Sun, 27 Jun 2021 20:10:22 +0200 -tinc (1.0.36-1) unstable; urgency=medium +tinc (1.1~pre17-1.2) experimental; urgency=medium - * New upstream version 1.0.36 - * Add Vcs tags to debian/control. + * Non-maintainer upload. + * Add patch to fix EVP_DecryptUpdate issue. Closes: #923438 + + -- Don Armstrong Sun, 31 May 2020 15:11:34 -0700 + +tinc (1.1~pre17-1.1) experimental; urgency=medium + + * Non-maintainer upload. + * Fix systemd service install path. Closes: #910618 + * Fix typo in --enable-miniupnpc option. + + -- Shengjing Zhu Wed, 10 Oct 2018 10:58:42 +0800 + +tinc (1.1~pre17-1) experimental; urgency=medium + + * New upstream release. + - Includes fixes for CVE-2018-16737, CVE-2018-16738. + - The GUI is no longer part of upstream, so has been removed. + * Link with the miniupnpc library. * Bump Standards-Version. + * Bump debian/compat. - -- Guus Sliepen Mon, 26 Aug 2019 14:17:21 +0200 + -- Guus Sliepen Mon, 08 Oct 2018 16:32:57 +0200 -tinc (1.0.35-2) unstable; urgency=medium - - * Bump Standards-Version and Build-Depend on debhelper-compat (= 12). - * Remove calls to dh_installinit and dh_systemd_start from debian/rules, - compat level 12 does the right thing by default. - * Ensure we clean up doc/tinc.info. - - -- Guus Sliepen Mon, 28 Jan 2019 21:54:45 +0100 - -tinc (1.0.35-1) unstable; urgency=medium - - * New upstream release. - - Includes fixes for CVE-2018-16737, CVE-2018-16738, CVE-2018-16758. - - -- Guus Sliepen Mon, 08 Oct 2018 16:09:06 +0200 - -tinc (1.0.34-1) unstable; urgency=medium - - [ Guus Sliepen ] - * New upstream release. - - Fixes a potential segmentation fault when connecting to an IPv6 - peer via a proxy. Closes: #887401 - * Add support for the $EXTRA variable in /etc/default/tinc when using - systemd. Closes: #887116 - - [ Benda Xu ] - * Prevent possible incorrect IPv6 checksums due to function inlining. - Closes: #891400 - - -- Guus Sliepen Tue, 12 Jun 2018 23:00:49 +0200 - -tinc (1.0.33-1) unstable; urgency=medium +tinc (1.1~pre15-1) experimental; urgency=medium * New upstream release. + * Bump Standards-Version. + * Bump debian/compat. + * Don't use while loops checking PID files anymore, the tinc CLI will + wait properly for the daemon to start or stop. Closes: #772379, #832784 + * Clean up scripts as suggested by Dominik George. Closes: #832781 * Test for /etc/default/tinc before trying to source it. Closes: #777262 - * Use --runstatedir=/run. - -- Guus Sliepen Sat, 04 Nov 2017 16:22:06 +0100 + -- Guus Sliepen Tue, 05 Sep 2017 21:03:51 +0200 -tinc (1.0.32-1) unstable; urgency=medium - - * New upstream release. - * Add a note to new nets.boot files that it is not used with systemd. - Closes: #841052 - * In the post-down script, read the pid file only once. Closes: #832784 - * Explicitly use /bin/sleep from coreutils. Closes: #772379 - * Bump Standards-Version. - - -- Guus Sliepen Tue, 05 Sep 2017 20:23:36 +0200 - -tinc (1.0.31-1) unstable; urgency=medium +tinc (1.1~pre12-1) experimental; urgency=medium * New upstream release. * Bump Standards-Version. - * Bump debian/compat. - * Add missing Depends: lsb-base. + * Depend on python-wxgtk3.0 for the GUI. + * Use dh --with python2. + * Add Build-Depends for dh-python. + * Update links in debian/control and debian/copyright. - -- Guus Sliepen Sun, 15 Jan 2017 16:20:40 +0100 + -- Guus Sliepen Sun, 24 Apr 2016 14:51:14 +0200 -tinc (1.0.29-2) unstable; urgency=medium - - * Rebuild with libssl-dev from unstable. - - -- Guus Sliepen Thu, 27 Oct 2016 13:09:46 +0200 - -tinc (1.0.29-1) unstable; urgency=medium +tinc (1.1~pre11-1) experimental; urgency=medium * New upstream release. - * Bump debian/compat. - - -- Guus Sliepen Mon, 10 Oct 2016 22:30:25 +0200 - -tinc (1.0.28-1) unstable; urgency=medium - - * New upstream release. - - Fixes FTBFS on kfreebsd. - * Systemd service files are now provided by upstream. - - -- Guus Sliepen Sun, 10 Apr 2016 15:44:28 +0200 - -tinc (1.0.27-2) unstable; urgency=medium - - * Fix tinc@.service. - - -- Guus Sliepen Sun, 10 Apr 2016 12:45:33 +0200 - -tinc (1.0.27-1) unstable; urgency=medium - - * New upstream release. - * Bump Standards-Version. - * Add native systemd unit files. + * Update NEWS.Debian to reflect that tincctl has been renamed to tinc. + Closes: #729889 + * Warn about incompatibility with previous 1.1preX releases, and that new + Ed25519 keys should be generated. + * Add native systemd service files. * Automatically convert networks listed in nets.boot to systemd service instances on upgrade. + * Don't restart tinc on upgrade for now. - -- Guus Sliepen Sun, 10 Apr 2016 01:39:16 +0200 + -- Guus Sliepen Thu, 08 Jan 2015 14:51:34 +0100 -tinc (1.0.26-1) unstable; urgency=medium - - * New upstream release. - * Use the contents, not the presence, of the pidfile to check that tincd is - shut down properly. Closes: #774682 - * Bump Standards-Version. - - -- Guus Sliepen Sun, 05 Jul 2015 17:23:08 +0200 - -tinc (1.0.24-2) unstable; urgency=medium - - * Improve the init script: stopping tinc now waits for the process to - terminate. If that doesn't happen in 5 seconds, it will send the TERM - signal again (which helps if tinc is waiting for a script to finish - executing). It now also detects whether the process mentioned in the PID - file is actually running, and if not it will exit early and without - warnings. Closes: #748107 - - -- Guus Sliepen Wed, 14 May 2014 21:44:16 +0200 - -tinc (1.0.24-1) unstable; urgency=medium - - [ Guus Sliepen ] - * New upstream release - * Add a debian/watch file. - * Bump Standards-Version. - - [ Gian Piero Carrubba ] - * Allow resource limits to be set in /etc/default/tinc. - Closes: #690685, #704702 - - -- Guus Sliepen Sun, 11 May 2014 21:17:13 +0200 - -tinc (1.0.23-2) unstable; urgency=low - - * Use if-statements instead of && in shell scripts. Closes: #731279 - The && operator does not clear the error status, and if the next statement - in a shell script does not change the error status it would cause the - script to prematurely exit. Thanks to Peter Reinholdtsen for spotting it. - * Use absolute path to tincd in the if-post-down script. - - -- Guus Sliepen Thu, 05 Dec 2013 09:41:13 +0000 - -tinc (1.0.23-1) unstable; urgency=low +tinc (1.1~pre9-1) experimental; urgency=low * New upstream release. - -- Guus Sliepen Sat, 19 Oct 2013 21:06:05 +0200 + -- Guus Sliepen Sun, 08 Sep 2013 18:00:28 +0200 -tinc (1.0.22-1) unstable; urgency=low +tinc (1.1~pre8-2) experimental; urgency=low + + * Run make clean after the configure step to get rid of .o files that were + accidentily left in the orig.tar.gz. + + -- Guus Sliepen Wed, 14 Aug 2013 16:13:43 +0200 + +tinc (1.1~pre8-1) experimental; urgency=low * New upstream release. - Handles whitespace between command line flags and optional arguments. @@ -167,20 +92,73 @@ tinc (1.0.22-1) unstable; urgency=low * Don't use texi2html anymore, use automake's install-html target which uses makeinfo. - -- Guus Sliepen Wed, 14 Aug 2013 15:34:29 +0200 + -- Guus Sliepen Wed, 14 Aug 2013 15:34:41 +0200 -tinc (1.0.21-1) unstable; urgency=low +tinc (1.1~pre7-2) experimental; urgency=low + + [ Gian Piero Carrubba ] + * Init script fails to pass extra arguments to tincd. Closes: #704701 + + Remove the '--' as it is passed unaltered to tincd, preventing it to read + trailing parameters. + + Pass extra arguments also when restarting the daemon. + * Set process limits when started by ifupdown. Closes: #704702 + + [ Guus Sliepen ] + * Check whether the tincd process is still running in the if-post-down script. + Closes: #704708 + + -- Guus Sliepen Wed, 01 May 2013 10:41:31 +0200 + +tinc (1.1~pre7-1) experimental; urgency=high * New upstream release. - - Includes fix for CVE-2013-1428. + - Drop packets forwarded via TCP if they are too big (CVE-2013-1428). - -- Guus Sliepen Sun, 05 May 2013 10:42:33 +0200 + -- Guus Sliepen Tue, 23 Apr 2013 11:37:38 +0200 -tinc (1.0.19-3) unstable; urgency=high +tinc (1.1~pre6-1) experimental; urgency=low - * Drop packets forwarded via TCP if they are too big (CVE-2013-1428). + * New upstream release. - -- Guus Sliepen Fri, 12 Apr 2013 22:52:10 +0200 + -- Guus Sliepen Wed, 20 Feb 2013 16:53:33 +0100 + +tinc (1.1~pre4-1) experimental; urgency=low + + [ Gian Piero Carrubba ] + * Allow resource limits to be set in /etc/default/tinc. Closes: #690685 + + [ Guus Sliepen ] + * New upstream release. + + -- Guus Sliepen Wed, 05 Dec 2012 23:05:01 +0100 + +tinc (1.1~pre3-1) experimental; urgency=low + + * New upstream release. + * Bump Standards-Version. + * Enable parallel builds. + * Bump debian/compat to 9, so tinc gets build with hardening flags. + * Move tinc-gui to its own package. + + -- Guus Sliepen Sun, 14 Oct 2012 23:51:21 +0200 + +tinc (1.1~pre2-2) experimental; urgency=low + + * add forgotten build-depend on libncurses5-dev for new `tincctl top' + * add libevent-dev build dependency + * remove build dependency on gettext + + -- Michael Tokarev Sun, 07 Aug 2011 17:32:39 +0400 + +tinc (1.1~pre2-1) experimental; urgency=low + + * first cut of 1.1-tobe. + Rewrote control scripts et al to use tincctl. + * build-depend on libssl >>1.0.0 to get proper EC support + * remove crypto-related symlinks from src/ in clean -- + probably should go into upstream makefile instead + + -- Michael Tokarev Sun, 07 Aug 2011 12:57:15 +0400 tinc (1.0.19-2) unstable; urgency=low diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +11 diff --git a/debian/control b/debian/control index 98c0678..20f6027 100644 --- a/debian/control +++ b/debian/control @@ -2,17 +2,13 @@ Source: tinc Section: net Priority: optional Maintainer: Guus Sliepen -Standards-Version: 4.4.0 -Build-Depends: libssl-dev, debhelper-compat (= 12), gettext, texinfo, zlib1g-dev, liblzo2-dev -Homepage: http://www.tinc-vpn.org/ -Vcs-Browser: https://salsa.debian.org/guus/tinc -Vcs-Git: https://salsa.debian.org/guus/tinc.git -Rules-Requires-Root: no +Standards-Version: 4.2.1 +Build-Depends: libssl-dev (>>1.0.0), debhelper (>= 11), texinfo, zlib1g-dev, liblzo2-dev, libncurses5-dev, libreadline-dev, libminiupnpc-dev +Homepage: https://www.tinc-vpn.org/ Package: tinc Architecture: any -Pre-Depends: ${misc:Pre-Depends} -Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base (>= 3.0-6) +Depends: ${shlibs:Depends}, ${misc:Depends} Description: Virtual Private Network daemon tinc is a daemon with which you can create a virtual private network (VPN). One daemon can handle multiple connections, so you can diff --git a/debian/copyright b/debian/copyright index c8a6fb8..02e3a92 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,14 +1,14 @@ This package was debianized by Ivo Timmermans on Fri, 21 Apr 2000 17:07:50 +0200. -It was downloaded from http://www.tinc-vpn.org/ +It was downloaded from https://www.tinc-vpn.org/ Upstream Authors: Guus Sliepen - Ivo Timmermans + Ivo Timmermans Copyright (C) 1998-2005 Ivo Timmermans - 1998-2008 Guus Sliepen + 1998-2016 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 @@ -28,7 +28,7 @@ all other requirements of the GPL are met. The following applies to the LZO library: Hereby I grant a special exception to the tinc VPN project -(http://tinc.nl.linux.org/) to link the LZO library with the OpenSSL library -(http://www.openssl.org). +(https://wwww.tinc-vpn.org/) to link the LZO library with the OpenSSL library +(https://openssl.org). Markus F.X.J. Oberhumer diff --git a/debian/patches/fix-incorrect-icmpv6-checksum b/debian/patches/fix-incorrect-icmpv6-checksum deleted file mode 100644 index 4448b88..0000000 --- a/debian/patches/fix-incorrect-icmpv6-checksum +++ /dev/null @@ -1,65 +0,0 @@ -Package: tinc -Version: 1.0.33-1 -Severity: important - -Dear Guus, - -I have been using tinc since 2009 and it is great! - -When PMTUDiscovery=yes and Mode=switch, and if ipv6 is used inside -tinc, the ICMPv6 "Packet Too Big" packets have incorrect checksums. -It can be reproduced by `ping6 -s 1800` and `tcpdump -i -`. Consequently, the host ignores the tinc-generated -ICMPv6 packets, PMTUDiscovery does not work and the connections freeze -when data flows are big. - -I find the bug is gone if the function "inet_checksum" in route.c is -not inlined, either by compiling tinc with "-O2 --fno-inline-functions", or apply a patch such as, - -diff --git a/src/route.c b/src/route.c -index ff82c06e..cd55383a 100644 ---- a/src/route.c -+++ b/src/route.c -@@ -60,7 +60,7 @@ static const size_t opt_size = sizeof(struct nd_opt_hdr); - - /* RFC 1071 */ - --static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) { -+__attribute__ ((noinline)) static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) { - uint16_t *p = data; - uint32_t checksum = prevsum ^ 0xFFFF; - - - -I have tested with gcc-7.3.0 and gcc-5.4.0. They behaved the same. I -am not good at assembly to find out what really happened, but it is -for sure that inet_checksum does not work as expected if compiled -inline. - -Thanks! - -Yours, -Benda - --- System Information: -Debian Release: buster/sid - APT prefers unstable - APT policy: (500, 'unstable') -Architecture: amd64 (x86_64) - -Kernel: Linux 4.9.0-5-amd64 (SMP w/8 CPU cores) -Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8) (ignored: LC_ALL set to en_US.UTF-8), LANGUAGE=en_US:en (charmap=UTF-8) (ignored: LC_ALL set to en_US.UTF-8) -Shell: /bin/sh linked to /bin/dash -Init: sysvinit (via /sbin/init) - -Versions of packages tinc depends on: -ii libc6 2.26-2 -ii liblzo2-2 2.08-1.2+b2 -ii libssl1.1 1.1.0g-2 -ii lsb-base 9.20170808 -ii zlib1g 1:1.2.8.dfsg-5 - -tinc recommends no packages. - -tinc suggests no packages. diff --git a/debian/patches/fix-version-number b/debian/patches/fix-version-number new file mode 100644 index 0000000..d51bb01 --- /dev/null +++ b/debian/patches/fix-version-number @@ -0,0 +1,11 @@ +--- a/configure.ac ++++ b/configure.ac +@@ -3,7 +3,7 @@ + origcflags="$CFLAGS" + + AC_PREREQ(2.69) +-AC_INIT([tinc], m4_esyscmd_s((git describe || echo UNKNOWN) | sed 's/release-//')) ++AC_INIT([tinc], [1.1~pre18]) + AC_CONFIG_SRCDIR([src/tincd.c]) + AM_INIT_AUTOMAKE([std-options subdir-objects nostdinc silent-rules -Wall]) + AC_CONFIG_HEADERS([config.h]) diff --git a/debian/patches/series b/debian/patches/series index a4c8974..02fa232 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1 @@ -fix-incorrect-icmpv6-checksum -support-etc-defaults-tinc +fix-version-number diff --git a/debian/patches/support-etc-defaults-tinc b/debian/patches/support-etc-defaults-tinc deleted file mode 100644 index 80b3d86..0000000 --- a/debian/patches/support-etc-defaults-tinc +++ /dev/null @@ -1,12 +0,0 @@ ---- a/systemd/tinc@.service.in -+++ b/systemd/tinc@.service.in -@@ -9,7 +9,8 @@ - [Service] - Type=simple - WorkingDirectory=@sysconfdir@/tinc/%i --ExecStart=@sbindir@/tincd -n %i -D -+EnvironmentFile=/etc/default/tinc -+ExecStart=@sbindir@/tincd -n %i -D $EXTRA - ExecReload=@sbindir@/tincd -n %i -kHUP - KillMode=mixed - Restart=on-failure diff --git a/debian/postinst b/debian/postinst index c4f5d53..e05f787 100644 --- a/debian/postinst +++ b/debian/postinst @@ -6,16 +6,8 @@ set -e case "$1" in configure) - if [ ! -e /dev/.devfsd ] ; then if [ ! -e /dev/.devfs ] ; then - if [ ! -e /dev/net/tun ] ; then if [ ! -e /dev/tun ] ; then if [ -e /dev/MAKEDEV ]; then - echo "Creating tun device..." - cd /dev && ./MAKEDEV net/tun 2>/dev/null || ./MAKEDEV tun 2>/dev/null || echo "Failed to create tun device." - fi; fi; fi - fi; fi - if [ ! -e $NETSFILE ] ; then - echo "## This file contains all names of the networks to be started on system startup when using sysvinit." > $NETSFILE - echo "## If you are using systemd, use systemctl enable tinc@netname to enable individual networks." >> $NETSFILE + echo "## This file contains all names of the networks to be started on system startup." > $NETSFILE fi ;; diff --git a/debian/preinst b/debian/preinst index 721a2bd..030c1d0 100644 --- a/debian/preinst +++ b/debian/preinst @@ -8,7 +8,7 @@ set -e case "$1" in upgrade) - if dpkg --compare-versions "$2" '<<' "1.0.27-1"; then + if dpkg --compare-versions "$2" '<<' "1.1~pre11-1"; then if [ -f "$NETSFILE" ]; then echo -n "Creating systemd service instances from nets.boot:" mkdir -p "$WANTS" diff --git a/debian/rules b/debian/rules index ee2949d..47ecdcb 100755 --- a/debian/rules +++ b/debian/rules @@ -3,14 +3,19 @@ %: dh $@ -override_dh_clean: - dh_clean - rm -f doc/tinc.info - override_dh_auto_configure: - dh_auto_configure -- --enable-uml --with-systemd=/lib/systemd/system --runstatedir=/run + dh_auto_configure -- --enable-uml \ + --with-systemd=/lib/systemd/system/ + $(MAKE) clean override_dh_auto_install: dh_auto_install -- install-html # Remove info dir file rm -f debian/tinc/usr/share/info/dir + +override_dh_auto_test: + # Don't run the tests, it involves starting tinc daemons and making network connections. + # I don't think the autobuilders will like this. + +override_dh_installinit: + dh_installinit -r diff --git a/debian/tinc.default b/debian/tinc.default index bca2432..5c63ac5 100644 --- a/debian/tinc.default +++ b/debian/tinc.default @@ -4,4 +4,4 @@ # Limits to be configured for the tincd process. Please read your shell # (pointed by /bin/sh) documentation for ulimit. You probably want to raise the # max locked memory value if using both --mlock and --user flags. -# LIMITS="-l 1024" +# LIMITS="-l 128" diff --git a/debian/tinc.dirs b/debian/tinc.dirs deleted file mode 100644 index 1eae2e2..0000000 --- a/debian/tinc.dirs +++ /dev/null @@ -1,7 +0,0 @@ -usr/sbin -usr/share -etc -etc/init.d -usr/share/locale -usr/share/doc/tinc -etc/tinc diff --git a/debian/tinc.files b/debian/tinc.files deleted file mode 100644 index 3d376cc..0000000 --- a/debian/tinc.files +++ /dev/null @@ -1,6 +0,0 @@ -usr/sbin/tincd -usr/share/man -etc -usr/share/doc/tinc -usr/share/locale -usr/share/info diff --git a/debian/tinc.if-post-down b/debian/tinc.if-post-down index e173476..0a3602b 100755 --- a/debian/tinc.if-post-down +++ b/debian/tinc.if-post-down @@ -2,40 +2,12 @@ set -e -if [ "$METHOD" = loopback -o -z "$IF_TINC_NET" ]; then - exit 0 -fi +test "$METHOD" != loopback -a -n "$IF_TINC_NET" || exit 0 -# Determine location of the PID file - -EXTRA="" -if [ -n "$IF_TINC_PIDFILE" ]; then - EXTRA="--pidfile=$IF_TINC_PIDFILE" +if test -z "$IF_TINC_PIDFILE"; then + /usr/sbin/tinc -n "$IF_TINC_NET" stop $EXTRA else - IF_TINC_PIDFILE=/var/run/tinc.$IF_TINC_NET.pid + /usr/sbin/tinc -n "$IF_TINC_NET" --pidfile="$IF_TINC_PIDFILE" stop fi -# Stop the tinc daemon - -read pid rest < $IF_TINC_PIDFILE 2>/dev/null - -/usr/sbin/tincd -n "$IF_TINC_NET" -k $EXTRA - -# Wait for it to shut down properly - -/bin/sleep 0.1 -i=0; -while [ -f $IF_TINC_PIDFILE ] ; do - if [ ! -e "/proc/$pid" ] ; then - exit 0 - fi - - if [ $i = '30' ] ; then - echo 'Failed to stop tinc daemon!' - exit 1 - fi - /bin/sleep 0.1 - i=$(($i+1)) -done - exit 0 diff --git a/debian/tinc.if-pre-up b/debian/tinc.if-pre-up index 68a3b53..06a863b 100755 --- a/debian/tinc.if-pre-up +++ b/debian/tinc.if-pre-up @@ -2,13 +2,13 @@ set -e -if [ "$METHOD" = loopback -o -z "$IF_TINC_NET" ]; then - exit 0 -fi +test -n "$IF_TINC_NET" || exit 0 # Read options from /etc/default -[ -r /etc/default/tinc ] && . /etc/default/tinc +if test -e /etc/default/tinc; then + . /etc/default/tinc +fi # Set process limits @@ -23,48 +23,24 @@ setlimits() { fi done } - test -n "$LIMITS" && setlimits $LIMITS # Read options from /etc/network/interfaces -[ -n "$IF_TINC_CONFIG" ] && EXTRA="$EXTRA -c $IF_TINC_CONFIG" -[ -n "$IF_TINC_DEBUG" ] && EXTRA="$EXTRA -d$IF_TINC_DEBUG" -[ -n "$IF_TINC_MLOCK" ] && EXTRA="$EXTRA --mlock" -[ -n "$IF_TINC_LOGFILE" ] && EXTRA="$EXTRA --logfile=$IF_TINC_LOGFILE" -[ -n "$IF_TINC_PIDFILE" ] && EXTRA="$EXTRA --pidfile=$IF_TINC_PIDFILE" || IF_TINC_PIDFILE=/var/run/tinc.$IF_TINC_NET.pid -[ -n "$IF_TINC_CHROOT" ] && EXTRA="$EXTRA --chroot" -[ -n "$IF_TINC_USER" ] && EXTRA="$EXTRA --user=$IF_TINC_USER" - +test -z "$IF_TINC_CONFIG" || EXTRA="$EXTRA -c $IF_TINC_CONFIG" +test -z "$IF_TINC_DEBUG" || EXTRA="$EXTRA -d$IF_TINC_DEBUG" +test -z "$IF_TINC_MLOCK" || EXTRA="$EXTRA --mlock" +test -z "$IF_TINC_LOGFILE" || EXTRA="$EXTRA --logfile=$IF_TINC_LOGFILE" +test -z "$IF_TINC_PIDFILE" || EXTRA="$EXTRA --pidfile=$IF_TINC_PIDFILE" || IF_TINC_PIDFILE=/var/run/tinc.$IF_TINC_NET.pid +test -z "$IF_TINC_CHROOT" || EXTRA="$EXTRA --chroot" +test -z "$IF_TINC_USER" || EXTRA="$EXTRA --user=$IF_TINC_USER" # Start tinc daemon -/usr/sbin/tincd -n "$IF_TINC_NET" -o "Interface=$IFACE" $EXTRA - -# Wait for it to come up properly - -/bin/sleep 0.1 -i=0; -while [ ! -f $IF_TINC_PIDFILE ] ; do - if [ $i = '30' ] ; then - echo 'Failed to start tinc daemon!' - exit 1 - fi - /bin/sleep 0.1 - i=$(($i+1)) -done - -while read pid rest < $IF_TINC_PIDFILE ; do - if [ -e "/proc/$pid" ] ; then - exit 0 - fi - - if [ $i = '30' ] ; then - echo 'Failed to start tinc daemon!' - exit 1 - fi - /bin/sleep 0.1 - i=$(($i+1)) -done +if test -z "$IF_TINC_PIDFILE"; then + /usr/sbin/tinc -n "$IF_TINC_NET" start -o "Interface=$IFACE" $EXTRA +else + /usr/sbin/tinc -n "$IF_TINC_NET" --pidfile="$IF_TINC_PIDFILE" start -o "Interface=$IFACE" $EXTRA +fi exit 0 diff --git a/debian/tinc.if-up b/debian/tinc.if-up index 364f88f..5c5cb43 100755 --- a/debian/tinc.if-up +++ b/debian/tinc.if-up @@ -2,8 +2,10 @@ set -e -if [ "$METHOD" = loopback -o -n "$IF_TINC_NET" ]; then - exit 0 -fi +test "$METHOD" != loopback -a -n "$IF_TINC_NET" || exit 0 -invoke-rc.d tinc alarm || exit 0 +if test -z "$IF_TINC_PIDFILE"; then + /usr/sbin/tinc -n "$IF_TINC_NET" retry +else + /usr/sbin/tinc -n "$IF_TINC_NET" --pidfile="$IF_TINC_PIDFILE" retry +fi diff --git a/debian/tinc.init b/debian/tinc.noinit similarity index 70% rename from debian/tinc.init rename to debian/tinc.noinit index 44ff4cf..5ec9721 100644 --- a/debian/tinc.init +++ b/debian/tinc.noinit @@ -21,6 +21,7 @@ . /lib/lsb/init-functions DAEMON="/usr/sbin/tincd" +CONTROL="/usr/sbin/tinc" NAME="tinc" DESC="tinc daemons" TCONF="/etc/tinc" @@ -41,7 +42,10 @@ foreach_net() { shift egrep '^[ ]*[a-zA-Z0-9_-]+' $NETSFILE | while read net args; do echo -n " $net" - "$@" $net $args + case "$1" in + start|restart) $CONTROL -n $net $1 $EXTRA $args ;; + *) $CONTROL -n $net $1 ;; + esac done echo "." } @@ -49,7 +53,7 @@ foreach_net() { signal_running() { for i in /var/run/tinc.*pid; do if [ -f "$i" ]; then - head -1 $i | while read pid; do + head -1 $i | while read pid junk; do kill -$1 $pid done fi @@ -70,51 +74,6 @@ setlimits() { test -n "$LIMITS" && setlimits $LIMITS -start() { - $DAEMON $EXTRA -n "$@" -} - -stop() { - [ -f /var/run/tinc.$1.pid ] || return - read pid rest /dev/null || return - - $DAEMON -n $1 -k || return - - i=0; - /bin/sleep 0.5 - - # Wait for the pidfile to disappear - while [ -f /var/run/tinc.$1.pid ]; do - # And check that there is an actual process running - kill -0 "$pid" 2>/dev/null || return - - if [ $i = '10' ] ; then - # It's still alive, kill it again and give up - $DAEMON -n $1 -k && /bin/sleep 0.5 - break - else - echo -n "." - i=$(($i+1)) - fi - - /bin/sleep 0.5 - done -} - -reload() { - $DAEMON -n $1 -kHUP -} - -alarm() { - $DAEMON -n $1 -kALRM -} - -restart() { - stop "$@" - start "$@" -} - case "$1" in start) foreach_net "Starting $DESC:" start @@ -128,11 +87,15 @@ case "$1" in restart) foreach_net "Restarting $DESC:" restart ;; - alarm) + force-restart) + $0 stop + $0 start + ;; + retry) signal_running ALRM ;; *) - echo "Usage: /etc/init.d/$NAME {start|stop|reload|restart|force-reload|alarm}" + echo "Usage: /etc/init.d/$NAME {start|stop|reload|restart|force-reload|retry}" exit 1 ;; esac diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc deleted file mode 100644 index a79b551..0000000 --- a/debian/upstream/signing-key.asc +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 - -mQINBEVZzTABEADawFv/ibQ48uA1eRhL07vdM36Pcq2HDyuyNA+vYEalNH4jLmha -nGPvDALWwX0DXIWDAG8zeTj8s8zliLIjuPS4WRI2YdKIvWeG9fEvpKSXVWa42ica -dpzn/H2aBd4Ax6rQaiTXKOvxANAp2Veb+73ssPV5AL00uGTpzhh98xQzfFQCZ9ZL -YSVPqbMed5oCt/4jM9FBO2CuBtkfiO0dVVYtW7FAVjcIlE2NaZol/KGvz7wsS+yA -dE42W0l5MdQueLmTF4AIUSSTyPFGMQoyqh+MPif73Y589IGcW6GdfKYWnVDo6Trh -HOhcEky+uTYKb6NL0vrJgUiYVMzAOXFDdJni1eQGO6EfZRNYtTga3alWq/jVelK7 -BID5JyNbkrTAqdPnhJGivVyk8gGX3+Hng8rkXTfWhp6yAYau1QfBm0F2tIQmpL1C -+7DoLvFErboQf685jXJBjzyvsJxB2ZLH1OOw6mNL0hy2LFkIyGza/bktY2em4apo -KWV5AM5LpyW6THH1oDS7706xFNkf4IFhKE0hKPzBiRnMDjRtMI131lkc5+P8Lqf5 -jLTCFUgbEhU6Dz2YjhHAumVm0NWJETUpFDtVvMrqk+mp5xldUOWRNlYQC5aQOyda -eHOlNzB/BFbPhKMWI/zWlEH6f0t6WjHb4iwpWO47511aVNHF59aPzbAHxwARAQAB -tC9UaW5jIERldmVsb3BtZW50IFRlYW0gPHRpbmMtZGV2ZWxAdGluYy12cG4ub3Jn -PokCNgQTAQIAIAUCRVnNMAIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEEpg -hLnA1x9KxOAQAMPhO2WTXv9AaGQBQhyyoGseKoSfcRjtI9YEfE0OInQQ2E+SPYF/ -dkfeLGAUqa4foTYQlMqfw70vBhTdVCb7HLTGn6/ICFUtDwakUQCgUQGlUVQiondA -8W6jj+QWrh5BQhHuXy2nCVaKnksu0/xQPsJotghxuYqaot3iJ4qVw9iT/Yeo9irH -Ffftqu0OVCdQ5zQuFNI9YCv/C83L59ecH7bOzYz2efTyobvxrhmgQtgOQwCWOE09 -Kr3veM9KLG33YoXlqv8QN/3CwtsJvSwfhj+R2JowZC7RqHe/yznRzmRMsgbrolxi -uiJLonGxcNNkD7WhXi7/zL4A/JaC27XrFf1MPLv8aiTwnOIfrXd5jawpauUOZOXK -UjIZHlIqviB0r5s7A+AFeHW38Tp9VzNuQ7aEl5bHbSXV+s7DTAwGb279z0AVaJ3h -5hWFxupw7UoGdzROwCBYzeX3sioAL6QvkXua8X6zOH45VEiSwrRX3Hlx/MLo7Z4g -PWgFLf6WviHQXCG834To1XIwUk9RgNsL7OzGjtaCuRymADImKMNwierq2zTfoRFQ -v9onIadIMF1fsc70hiBG9yAi36MEuk4VxG8Jc3PQQfSFm4RWazv1k3E0AAIob2Ek -DLVjvLO62Navo/OQ6D7yyiX0qes5S7/k3F1h8+87eRn3yxZE2kbxPg8KuQENBEVZ -zYYBCACoYgExmMKEtABL3pir/IXI4exR/45hkP6txFIrkVQb8DGJ9H39kiDr4gtj -F+pvcgMpec35S3m4fJ7WhDNvbDB4ZvONc1NMKs9cqDXfB+TLWPm4W+SkcKUrs+nK -i8l7ieggTALJcvRoLTX2EFmUFrD6y870W3rK2SfC/0VJX8Ou5cPbwCeueExjo5x0 -3gNfu0jWUS0cyA9y+1rVVekrLuJY/+SI4jv3gdzrYbxD0f6dpbpC37QHoJdd0KGF -acPtT2azjY+HxW10EA3Aw9oZWFXnb1nFg2RbDpkaGLmLbaXweltLyw154UmCXxym -PXPqi9bSfEkj0GiYrapk4mW114u9ABEBAAGJA0QEGAECAA8FAkVZzYcCGwIFCQHh -M4ABKQkQSmCEucDXH0rAXSAEGQECAAYFAkVZzYcACgkQ017duBDeLmmu3wf/TFZM -tLKxiyQPtGSTE3eULPdWCEMDZXdSPTY0QHM7Zvh+qn1ei+iFpD0549MYnBEAwDIV -4o62Nrcg8IPu0CEkZIAn2JiFFGYvMGk91awZGV1GS3umd7Dt349E1oKDPZVzRn+j -QIKarXxxabRmx2s/dZZgSs/eguWTboBFmvls9tVfe8x/xPvcHFmqHVUoGKEJy6Tm -no+s4yNjpc5wAaRml+GYf1tK9WMFuZn79qKxkYv4WI96dXR0FL59YbFRkkaI+1/b -G31MvWTqVjY1nJGslByFPUUB8Ca1djtp1gx6NXnOYr3V5MEDfdbjjPu79No+/y6M -tIua5yNtSRlk2nKhIqo3D/sGQ/uHijiMRAtoLhiyqYMc9W9vx/KOoCUHoMJqE39a -Eputvgwwo0rwgSmNfB1dzneFojBdJ47WBSHC8NKtkpJj6mzvvlaI9jmEpK54x3nl -OmBj3X/xaepoRGAtrmgTBP4A9hSjrTMGu/tUQjlX+Xcr6n3g7GSVe5FpUyIMPXEU -+JZDTiEUyn1OdzfNGmnWP+KJX5pQVq4czf5XK6otLZJELDVw/Hbjnjmdz5WOKgEH -jSof3WPeMBgPAcd/cMAZeiGtWM1UpJBHqEXTkthmrpmtRftJKDbooNM9d5OwKiT+ -I3vNBv/plMSr8NDjhTWC+ihrN6RBj1vaqpDgGpucPhoO2hwNqWuI4q5WUKtCyw6J -An7ErNaH4c0nVaZq6kJ8Vxdt+LpKYmOsJ8y9xtl6StcrkKNn+6ZfCfee7DEo2bvb -scfpqJqqOqb5KOagTpYs8mo6yQ3leRvFtoeBOQHVQzct23EwOMxbDyeunT50a1eH -MZTEWuIiuCUudM81QXU46oIcaAtj8tpG0Tmnku9g27EjwxUt+8kVmhlPHMb2TdoF -k5d0ce+JuxIG5+i+t8sUCorMc8zfp4g1bxZeMOcPnfyO3bSW11EVcN7ZxHlFC+XE -YOByP50CM3P8lItPlQ+WpY9kYWjiwu9kVPaGQ16G47nspngJInU5dhka3KJGtIZe -q7kBDQRFWc3GAQgAz/JUs24niKsd0ZtOjUWd4R4y0gjS0kC/vvLL2gbXYkA9tcjG -5iEvVWJY34QsudX0v1ULsVnJ89X/gsk8hAhAia1TQm+0Qq+MSxEaQkMSaOO8N70U -XzgH1dielvpea20WO4MyWQuRIJ0K2nsGmEPnt6ZP7fK8HVHFNggP/mQ6LMu1reAF -gvb8cEtjb9B5QaCUEoLjsbsuo+vLW7AWy4GfIRNESHw2Qt08AUaGrihpu6zi7N2b -QkHHJtqa63GzYJ+kYzAVvrFJXdwL/TRXLBzv+6yyvYSIHi8uB6/o6CZN6hpjfpME -ph+oYPqXeNOyMd1E9Kg8ymcPrNyQKfE3WnCRXQARAQABiQIlBBgBAgAPBQJFWc3H -AhsMBQkB4TOAAAoJEEpghLnA1x9KXYMP/3D9JYti6C8DWuX2hWv+2SUbLcMa5u1e -ETewIo5S3mqGWSaxX4YLPVRkZ1lOLmFysOLimf0thYm7IsatLcWmBdYUpK92ilvr -Sk2sKlhrOoBXEX/79Kz+Aj5PjeyiQxgQ7Ba/afwhU6aTqs2Dp1T0YNu2eBvV+1QH -lZnW2Wh96zzwc0dJUuY9eBk5Fgpu08Wce7o6jxFPtVyAaQa0zcNAmw2TSY7wbunJ -WTl9OgATAyqVCuhWW7AnPcSqkH8lQigSc00wfqJKOD2Us0FqN6UKDCZTyQkdP6hx -G+3aUkkvGSOxy7u9bVWmrikuJMoiIY/CH/m+GcJJATVibI7t5MhhtBGySKc3PAwc -58sVe9AnbDvs84efrNP59j2KG2KKqcqjTGAmKyJnG3N50xJGakDIsqfneIRNWpEE -pOOPaOqR4qnPAS9OSt9A3hqBpWPjQcScd6AuO+J2PE5y23pTnb9PtLBQb5e4VFiv -N54u8j4bU2CO0SC2isZ3CR68dPHthWsxt4XbjhgktjbpQRQ8L2EDBQ8oE13GOrOw -P8x4Q0eC5uzkXNyRqzKOsxlZEuc+75aDDd3M2GDw08FG7GI1p/L+NgenwT+9TmFX -2hOKVrjgyx+fPut1t5DJJubDWXGOK33qvrL7HTCEv1zz7LNbbPwwNHK4lfMAWuJW -D9wc3Y9Se/P/ -=5MAs ------END PGP PUBLIC KEY BLOCK----- diff --git a/debian/watch b/debian/watch deleted file mode 100644 index f29609e..0000000 --- a/debian/watch +++ /dev/null @@ -1,2 +0,0 @@ -version=3 -opts=pgpsigurlmangle=s/$/.sig/ http://www.tinc-vpn.org/packages/tinc-(1\.0\.\d+)\.tar\.gz diff --git a/doc/Makefile.am b/doc/Makefile.am index 29d6ffd..e5f3a04 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -3,20 +3,23 @@ info_TEXINFOS = tinc.texi tinc_TEXINFOS = tincinclude.texi -man_MANS = tincd.8 tinc.conf.5 +man_MANS = tincd.8 tinc.8 tinc.conf.5 tinc-gui.8 -EXTRA_DIST = tincinclude.texi.in tincd.8.in tinc.conf.5.in sample-config +EXTRA_DIST = tincinclude.texi.in tincd.8.in tinc.8.in tinc.conf.5.in tinc-gui.8.in sample-config -CLEANFILES = *.html tincd.8 tinc.conf.5 tincinclude.texi - -texi2html: tinc.texi - $(AM_V_GEN)texi2html -split=chapter $< +CLEANFILES = *.html tincd.8 tinc.8 tinc.conf.5 tinc-gui.8 tincinclude.texi tincd.8.html: tincd.8 - $(AM_V_GEN)w3mman2html $< > $@ + $(AM_V_GEN)w3mman2html $? > $@ + +tinc.8.html: tinc.8 + $(AM_V_GEN)w3mman2html $? > $@ + +tinc-gui.8.html: tinc-gui.8 + $(AM_V_GEN)w3mman2html $? > $@ tinc.conf.5.html: tinc.conf.5 - $(AM_V_GEN)w3mman2html $< > $@ + $(AM_V_GEN)w3mman2html $? > $@ substitute = sed \ -e s,'@PACKAGE\@',"$(PACKAGE)",g \ @@ -28,6 +31,12 @@ substitute = sed \ tincd.8: $(srcdir)/tincd.8.in $(AM_V_GEN)$(substitute) $(srcdir)/tincd.8.in > $@ +tinc.8: $(srcdir)/tinc.8.in + $(AM_V_GEN)$(substitute) $(srcdir)/tinc.8.in > $@ + +tinc-gui.8: $(srcdir)/tinc-gui.8.in + $(AM_V_GEN)$(substitute) $(srcdir)/tinc-gui.8.in > $@ + tinc.conf.5: $(srcdir)/tinc.conf.5.in $(AM_V_GEN)$(substitute) $(srcdir)/tinc.conf.5.in > $@ diff --git a/doc/Makefile.in b/doc/Makefile.in index 81e9c93..5e74995 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.2 from Makefile.am. +# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -94,9 +94,12 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ $(top_srcdir)/m4/ax_check_compile_flag.m4 \ $(top_srcdir)/m4/ax_check_link_flag.m4 \ - $(top_srcdir)/m4/ax_require_defined.m4 $(top_srcdir)/m4/lzo.m4 \ - $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/zlib.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) @@ -209,8 +212,15 @@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ @@ -219,17 +229,21 @@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ MKDIR_P = @MKDIR_P@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ @@ -240,6 +254,8 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -298,9 +314,9 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ info_TEXINFOS = tinc.texi tinc_TEXINFOS = tincinclude.texi -man_MANS = tincd.8 tinc.conf.5 -EXTRA_DIST = tincinclude.texi.in tincd.8.in tinc.conf.5.in sample-config -CLEANFILES = *.html tincd.8 tinc.conf.5 tincinclude.texi +man_MANS = tincd.8 tinc.8 tinc.conf.5 tinc-gui.8 +EXTRA_DIST = tincinclude.texi.in tincd.8.in tinc.8.in tinc.conf.5.in tinc-gui.8.in sample-config +CLEANFILES = *.html tincd.8 tinc.8 tinc.conf.5 tinc-gui.8 tincinclude.texi substitute = sed \ -e s,'@PACKAGE\@',"$(PACKAGE)",g \ -e s,'@VERSION\@',"$(VERSION)",g \ @@ -830,18 +846,27 @@ uninstall-man: uninstall-man5 uninstall-man8 .PRECIOUS: Makefile -texi2html: tinc.texi - $(AM_V_GEN)texi2html -split=chapter $< - tincd.8.html: tincd.8 - $(AM_V_GEN)w3mman2html $< > $@ + $(AM_V_GEN)w3mman2html $? > $@ + +tinc.8.html: tinc.8 + $(AM_V_GEN)w3mman2html $? > $@ + +tinc-gui.8.html: tinc-gui.8 + $(AM_V_GEN)w3mman2html $? > $@ tinc.conf.5.html: tinc.conf.5 - $(AM_V_GEN)w3mman2html $< > $@ + $(AM_V_GEN)w3mman2html $? > $@ tincd.8: $(srcdir)/tincd.8.in $(AM_V_GEN)$(substitute) $(srcdir)/tincd.8.in > $@ +tinc.8: $(srcdir)/tinc.8.in + $(AM_V_GEN)$(substitute) $(srcdir)/tinc.8.in > $@ + +tinc-gui.8: $(srcdir)/tinc-gui.8.in + $(AM_V_GEN)$(substitute) $(srcdir)/tinc-gui.8.in > $@ + tinc.conf.5: $(srcdir)/tinc.conf.5.in $(AM_V_GEN)$(substitute) $(srcdir)/tinc.conf.5.in > $@ diff --git a/doc/sample-config/tinc.conf b/doc/sample-config/tinc.conf index 7081764..25a61a7 100644 --- a/doc/sample-config/tinc.conf +++ b/doc/sample-config/tinc.conf @@ -16,7 +16,7 @@ Name = alpha ConnectTo = beta # The tap device tinc will use. -# /dev/tap0 for ethertap, FreeBSD or OpenBSD -# /dev/tun0 for Solaris -# /dev/net/tun for Linux tun/tap +# Default is /dev/tap0 for ethertap or FreeBSD, +# /dev/tun0 for Solaris and OpenBSD, +# and /dev/net/tun for Linux tun/tap device. Device = /dev/net/tun diff --git a/doc/texinfo.tex b/doc/texinfo.tex index deca599..3c7051d 100644 --- a/doc/texinfo.tex +++ b/doc/texinfo.tex @@ -3,9 +3,9 @@ % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % -\def\texinfoversion{2020-02-11.09} +\def\texinfoversion{2020-10-24.12} % -% Copyright 1985, 1986, 1988, 1990-2019 Free Software Foundation, Inc. +% Copyright 1985, 1986, 1988, 1990-2020 Free Software Foundation, Inc. % % This texinfo.tex file is free software: you can redistribute it and/or % modify it under the terms of the GNU General Public License as @@ -33,7 +33,7 @@ % The texinfo.tex in any given distribution could well be out % of date, so if that's what you're using, please check. % -% Send bug reports to bug-texinfo@gnu.org. Please include including a +% Send bug reports to bug-texinfo@gnu.org. Please include a % complete document in each bug report with which we can reproduce the % problem. Patches are, of course, greatly appreciated. % @@ -349,34 +349,19 @@ \ifodd\pageno \advance\hoffset by \bindingoffset \else \advance\hoffset by -\bindingoffset\fi % + \checkchapterpage + % % Retrieve the information for the headings from the marks in the page, % and call Plain TeX's \makeheadline and \makefootline, which use the % values in \headline and \footline. % - % This is used to check if we are on the first page of a chapter. - \ifcase1\the\savedtopmark\fi - \let\prevchaptername\thischaptername - \ifcase0\firstmark\fi - \let\curchaptername\thischaptername - % - \ifodd\pageno \getoddheadingmarks \else \getevenheadingmarks \fi - % - \ifx\curchaptername\prevchaptername - \let\thischapterheading\thischapter - \else - % \thischapterheading is the same as \thischapter except it is blank - % for the first page of a chapter. This is to prevent the chapter name - % being shown twice. - \def\thischapterheading{}% - \fi - % % Common context changes for both heading and footing. % Do this outside of the \shipout so @code etc. will be expanded in % the headline as they should be, not taken literally (outputting ''code). \def\commonheadfootline{\let\hsize=\txipagewidth \texinfochars} % + \ifodd\pageno \getoddheadingmarks \else \getevenheadingmarks \fi \global\setbox\headlinebox = \vbox{\commonheadfootline \makeheadline}% - % \ifodd\pageno \getoddfootingmarks \else \getevenfootingmarks \fi \global\setbox\footlinebox = \vbox{\commonheadfootline \makefootline}% % @@ -423,6 +408,22 @@ \ifr@ggedbottom \kern-\dimen@ \vfil \fi} } +% Check if we are on the first page of a chapter. Used for printing headings. +\newif\ifchapterpage +\def\checkchapterpage{% + % Get the chapter that was current at the end of the last page + \ifcase1\the\savedtopmark\fi + \let\prevchaptername\thischaptername + % + \ifodd\pageno \getoddheadingmarks \else \getevenheadingmarks \fi + \let\curchaptername\thischaptername + % + \ifx\curchaptername\prevchaptername + \chapterpagefalse + \else + \chapterpagetrue + \fi +} % Argument parsing @@ -1010,7 +1011,7 @@ where each line of input produces a line of output.} \let\setfilename=\comment % @bye. -\outer\def\bye{\pagealignmacro\tracingstats=1\ptexend} +\outer\def\bye{\chappager\pagelabels\tracingstats=1\ptexend} \message{pdf,} @@ -1137,6 +1138,45 @@ where each line of input produces a line of output.} \fi +% Output page labels information. +% See PDF reference v.1.7 p.594, section 8.3.1. +\ifpdf +\def\pagelabels{% + \def\title{0 << /P (T-) /S /D >>}% + \edef\roman{\the\romancount << /S /r >>}% + \edef\arabic{\the\arabiccount << /S /D >>}% + % + % Page label ranges must be increasing. Remove any duplicates. + % (There is a slight chance of this being wrong if e.g. there is + % a @contents but no @titlepage, etc.) + % + \ifnum\romancount=0 \def\roman{}\fi + \ifnum\arabiccount=0 \def\title{}% + \else + \ifnum\romancount=\arabiccount \def\roman{}\fi + \fi + % + \ifnum\romancount<\arabiccount + \pdfcatalog{/PageLabels << /Nums [\title \roman \arabic ] >> }\relax + \else + \pdfcatalog{/PageLabels << /Nums [\title \arabic \roman ] >> }\relax + \fi +} +\else + \let\pagelabels\relax +\fi + +\newcount\pagecount \pagecount=0 +\newcount\romancount \romancount=0 +\newcount\arabiccount \arabiccount=0 +\ifpdf + \let\ptxadvancepageno\advancepageno + \def\advancepageno{% + \ptxadvancepageno\global\advance\pagecount by 1 + } +\fi + + % PDF uses PostScript string constants for the names of xref targets, % for display in the outlines, and in other places. Thus, we have to % double any backslashes. Otherwise, a name like "\node" will be @@ -1427,7 +1467,13 @@ output) for that.)} % subentries, which we calculated on our first read of the .toc above. % % We use the node names as the destinations. + % + % Currently we prefix the section name with the section number + % for chapter and appendix headings only in order to avoid too much + % horizontal space being required in the PDF viewer. \def\numchapentry##1##2##3##4{% + \dopdfoutline{##2 ##1}{count-\expnumber{chap##2}}{##3}{##4}}% + \def\unnchapentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{chap##2}}{##3}{##4}}% \def\numsecentry##1##2##3##4{% \dopdfoutline{##1}{count-\expnumber{sec##2}}{##3}{##4}}% @@ -1669,9 +1715,13 @@ output) for that.)} % Therefore, we read toc only once. % % We use node names as destinations. + % + % Currently we prefix the section name with the section number + % for chapter and appendix headings only in order to avoid too much + % horizontal space being required in the PDF viewer. \def\partentry##1##2##3##4{}% ignore parts in the outlines \def\numchapentry##1##2##3##4{% - \dopdfoutline{##1}{1}{##3}{##4}}% + \dopdfoutline{##2 ##1}{1}{##3}{##4}}% \def\numsecentry##1##2##3##4{% \dopdfoutline{##1}{2}{##3}{##4}}% \def\numsubsecentry##1##2##3##4{% @@ -1683,7 +1733,8 @@ output) for that.)} \let\appsecentry\numsecentry% \let\appsubsecentry\numsubsecentry% \let\appsubsubsecentry\numsubsubsecentry% - \let\unnchapentry\numchapentry% + \def\unnchapentry##1##2##3##4{% + \dopdfoutline{##1}{1}{##3}{##4}}% \let\unnsecentry\numsecentry% \let\unnsubsecentry\numsubsecentry% \let\unnsubsubsecentry\numsubsubsecentry% @@ -2496,7 +2547,7 @@ end \def\it{\fam=\itfam \setfontstyle{it}} \def\sl{\fam=\slfam \setfontstyle{sl}} \def\bf{\fam=\bffam \setfontstyle{bf}}\def\bfstylename{bf} -\def\tt{\fam=\ttfam \setfontstyle{tt}} +\def\tt{\fam=\ttfam \setfontstyle{tt}}\def\ttstylename{tt} % Texinfo sort of supports the sans serif font style, which plain TeX does not. % So we set up a \sf. @@ -2987,10 +3038,18 @@ end % arg (if given), and not the url (which is then just the link target). \newif\ifurefurlonlylink +% The default \pretolerance setting stops the penalty inserted in +% \urefallowbreak being a discouragement to line breaking. Set it to +% a negative value for this paragraph only. Hopefully this does not +% conflict with redefinitions of \par done elsewhere. +\def\nopretolerance{% +\pretolerance=-1 +\def\par{\endgraf\pretolerance=100 \let\par\endgraf}% +} + % The main macro is \urefbreak, which allows breaking at expected -% places within the url. (There used to be another version, which -% didn't support automatic breaking.) -\def\urefbreak{\begingroup \urefcatcodes \dourefbreak} +% places within the url. +\def\urefbreak{\nopretolerance \begingroup \urefcatcodes \dourefbreak} \let\uref=\urefbreak % \def\dourefbreak#1{\urefbreakfinish #1,,,\finish} @@ -3101,14 +3160,14 @@ end % Allow a ragged right output to aid breaking long URL's. There can % be a break at the \allowbreak with no extra glue (if the existing stretch in -% the line is sufficent), a break at the \penalty100 with extra glue added +% the line is sufficient), a break at the \penalty with extra glue added % at the end of the line, or no break at all here. % Changing the value of the penalty and/or the amount of stretch affects how -% preferrable one choice is over the other. +% preferable one choice is over the other. \def\urefallowbreak{% - \allowbreak + \penalty0\relax \hskip 0pt plus 2 em\relax - \penalty300 + \penalty1000\relax \hskip 0pt plus -2 em\relax } @@ -3305,6 +3364,25 @@ end \def\sup{\ifmmode \expandafter\ptexsp \else \expandafter\finishsup\fi} \def\finishsup#1{$\ptexsp{\hbox{\switchtolllsize #1}}$}% +% provide this command from LaTeX as it is very common +\def\frac#1#2{{{#1}\over{#2}}} + +% @displaymath. +% \globaldefs is needed to recognize the end lines in \tex and +% \end tex. Set \thisenv as @end displaymath is seen before @end tex. +{\obeylines +\globaldefs=1 +\envdef\displaymath{% +\tex +\def\thisenv{\displaymath}% +$$% +} + +\def\Edisplaymath{$$ +\def\thisenv{\tex}% +\end tex +}} + % @inlinefmt{FMTNAME,PROCESSED-TEXT} and @inlineraw{FMTNAME,RAW-TEXT}. % Ignore unless FMTNAME == tex; then it is like @iftex and @tex, % except specified as a normal braced arg, so no newlines to worry about. @@ -3509,7 +3587,7 @@ end % @pounds{} is a sterling sign, which Knuth put in the CM italic font. % -\def\pounds{{\it\$}} +\def\pounds{\ifmonospace{\ecfont\char"BF}\else{\it\$}\fi} % @euro{} comes from a separate font, depending on the current style. % We use the free feym* fonts from the eurosym package by Henrik @@ -3658,11 +3736,19 @@ end \fi % Quotes. -\chardef\quotedblleft="5C -\chardef\quotedblright=`\" \chardef\quoteleft=`\` \chardef\quoteright=`\' +% only change font for tt for correct kerning and to avoid using +% \ecfont unless necessary. +\def\quotedblleft{% + \ifmonospace{\ecfont\char"10}\else{\char"5C}\fi +} + +\def\quotedblright{% + \ifmonospace{\ecfont\char"11}\else{\char`\"}\fi +} + \message{page headings,} @@ -3784,12 +3870,19 @@ end \newtoks\evenheadline % headline on even pages \newtoks\oddheadline % headline on odd pages +\newtoks\evenchapheadline% headline on even pages with a new chapter +\newtoks\oddchapheadline % headline on odd pages with a new chapter \newtoks\evenfootline % footline on even pages \newtoks\oddfootline % footline on odd pages % Now make \makeheadline and \makefootline in Plain TeX use those variables -\headline={{\textfonts\rm \ifodd\pageno \the\oddheadline - \else \the\evenheadline \fi}} +\headline={{\textfonts\rm + \ifchapterpage + \ifodd\pageno\the\oddchapheadline\else\the\evenchapheadline\fi + \else + \ifodd\pageno\the\oddheadline\else\the\evenheadline\fi + \fi}} + \footline={{\textfonts\rm \ifodd\pageno \the\oddfootline \else \the\evenfootline \fi}\HEADINGShook} \let\HEADINGShook=\relax @@ -3805,12 +3898,14 @@ end \def\evenheading{\parsearg\evenheadingxxx} \def\evenheadingxxx #1{\evenheadingyyy #1\|\|\|\|\finish} \def\evenheadingyyy #1\|#2\|#3\|#4\finish{% -\global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + \global\evenheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}} + \global\evenchapheadline=\evenheadline} \def\oddheading{\parsearg\oddheadingxxx} \def\oddheadingxxx #1{\oddheadingyyy #1\|\|\|\|\finish} \def\oddheadingyyy #1\|#2\|#3\|#4\finish{% -\global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}} + \global\oddheadline={\rlap{\centerline{#2}}\line{#1\hfil#3}}% + \global\oddchapheadline=\oddheadline} \parseargdef\everyheading{\oddheadingxxx{#1}\evenheadingxxx{#1}}% @@ -3877,37 +3972,34 @@ end \parseargdef\headings{\csname HEADINGS#1\endcsname} \def\headingsoff{% non-global headings elimination - \evenheadline={\hfil}\evenfootline={\hfil}% - \oddheadline={\hfil}\oddfootline={\hfil}% + \evenheadline={\hfil}\evenfootline={\hfil}\evenchapheadline={\hfil}% + \oddheadline={\hfil}\oddfootline={\hfil}\oddchapheadline={\hfil}% } \def\HEADINGSoff{{\globaldefs=1 \headingsoff}} % global setting \HEADINGSoff % it's the default % When we turn headings on, set the page number to 1. +\def\pageone{ + \global\pageno=1 + \global\arabiccount = \pagecount +} + % For double-sided printing, put current file name in lower left corner, % chapter name on inside top of right hand pages, document % title on inside top of left hand pages, and page numbers on outside top % edge of all pages. \def\HEADINGSdouble{% -\global\pageno=1 -\global\evenfootline={\hfil} -\global\oddfootline={\hfil} -\global\evenheadline={\line{\folio\hfil\thistitle}} -\global\oddheadline={\line{\thischapterheading\hfil\folio}} -\global\let\contentsalignmacro = \chapoddpage +\pageone +\HEADINGSdoublex } \let\contentsalignmacro = \chappager % For single-sided printing, chapter title goes across top left of page, % page number on top right. \def\HEADINGSsingle{% -\global\pageno=1 -\global\evenfootline={\hfil} -\global\oddfootline={\hfil} -\global\evenheadline={\line{\thischapterheading\hfil\folio}} -\global\oddheadline={\line{\thischapterheading\hfil\folio}} -\global\let\contentsalignmacro = \chappager +\pageone +\HEADINGSsinglex } \def\HEADINGSon{\HEADINGSdouble} @@ -3917,7 +4009,9 @@ end \global\evenfootline={\hfil} \global\oddfootline={\hfil} \global\evenheadline={\line{\folio\hfil\thistitle}} -\global\oddheadline={\line{\thischapterheading\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\evenchapheadline={\line{\folio\hfil}} +\global\oddchapheadline={\line{\hfil\folio}} \global\let\contentsalignmacro = \chapoddpage } @@ -3925,8 +4019,22 @@ end \def\HEADINGSsinglex{% \global\evenfootline={\hfil} \global\oddfootline={\hfil} -\global\evenheadline={\line{\thischapterheading\hfil\folio}} -\global\oddheadline={\line{\thischapterheading\hfil\folio}} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\evenchapheadline={\line{\hfil\folio}} +\global\oddchapheadline={\line{\hfil\folio}} +\global\let\contentsalignmacro = \chappager +} + +% for @setchapternewpage off +\def\HEADINGSsinglechapoff{% +\pageone +\global\evenfootline={\hfil} +\global\oddfootline={\hfil} +\global\evenheadline={\line{\thischapter\hfil\folio}} +\global\oddheadline={\line{\thischapter\hfil\folio}} +\global\evenchapheadline=\evenheadline +\global\oddchapheadline=\oddheadline \global\let\contentsalignmacro = \chappager } @@ -4841,7 +4949,7 @@ end % like the previous two, but they put @code around the argument. \def\docodeindex#1{\edef\indexname{#1}\parsearg\docodeindexxxx} -\def\docodeindexxxx #1{\doind{\indexname}{\code{#1}}} +\def\docodeindexxxx #1{\docind{\indexname}{#1}} % Used for the aux, toc and index files to prevent expansion of Texinfo @@ -5117,64 +5225,66 @@ end \let\lbracechar\{% \let\rbracechar\}% % + % + \let\do\indexnofontsdef + % % Non-English letters. - \def\AA{AA}% - \def\AE{AE}% - \def\DH{DZZ}% - \def\L{L}% - \def\OE{OE}% - \def\O{O}% - \def\TH{TH}% - \def\aa{aa}% - \def\ae{ae}% - \def\dh{dzz}% - \def\exclamdown{!}% - \def\l{l}% - \def\oe{oe}% - \def\ordf{a}% - \def\ordm{o}% - \def\o{o}% - \def\questiondown{?}% - \def\ss{ss}% - \def\th{th}% + \do\AA{AA}% + \do\AE{AE}% + \do\DH{DZZ}% + \do\L{L}% + \do\OE{OE}% + \do\O{O}% + \do\TH{TH}% + \do\aa{aa}% + \do\ae{ae}% + \do\dh{dzz}% + \do\exclamdown{!}% + \do\l{l}% + \do\oe{oe}% + \do\ordf{a}% + \do\ordm{o}% + \do\o{o}% + \do\questiondown{?}% + \do\ss{ss}% + \do\th{th}% % - \def\LaTeX{LaTeX}% - \def\TeX{TeX}% + \do\LaTeX{LaTeX}% + \do\TeX{TeX}% % - % Assorted special characters. \defglyph gives the control sequence a - % definition that removes the {} that follows its use. - \defglyph\atchar{@}% - \defglyph\arrow{->}% - \defglyph\bullet{bullet}% - \defglyph\comma{,}% - \defglyph\copyright{copyright}% - \defglyph\dots{...}% - \defglyph\enddots{...}% - \defglyph\equiv{==}% - \defglyph\error{error}% - \defglyph\euro{euro}% - \defglyph\expansion{==>}% - \defglyph\geq{>=}% - \defglyph\guillemetleft{<<}% - \defglyph\guillemetright{>>}% - \defglyph\guilsinglleft{<}% - \defglyph\guilsinglright{>}% - \defglyph\leq{<=}% - \defglyph\lbracechar{\{}% - \defglyph\minus{-}% - \defglyph\point{.}% - \defglyph\pounds{pounds}% - \defglyph\print{-|}% - \defglyph\quotedblbase{"}% - \defglyph\quotedblleft{"}% - \defglyph\quotedblright{"}% - \defglyph\quoteleft{`}% - \defglyph\quoteright{'}% - \defglyph\quotesinglbase{,}% - \defglyph\rbracechar{\}}% - \defglyph\registeredsymbol{R}% - \defglyph\result{=>}% - \defglyph\textdegree{o}% + % Assorted special characters. + \do\atchar{@}% + \do\arrow{->}% + \do\bullet{bullet}% + \do\comma{,}% + \do\copyright{copyright}% + \do\dots{...}% + \do\enddots{...}% + \do\equiv{==}% + \do\error{error}% + \do\euro{euro}% + \do\expansion{==>}% + \do\geq{>=}% + \do\guillemetleft{<<}% + \do\guillemetright{>>}% + \do\guilsinglleft{<}% + \do\guilsinglright{>}% + \do\leq{<=}% + \do\lbracechar{\{}% + \do\minus{-}% + \do\point{.}% + \do\pounds{pounds}% + \do\print{-|}% + \do\quotedblbase{"}% + \do\quotedblleft{"}% + \do\quotedblright{"}% + \do\quoteleft{`}% + \do\quoteright{'}% + \do\quotesinglbase{,}% + \do\rbracechar{\}}% + \do\registeredsymbol{R}% + \do\result{=>}% + \do\textdegree{o}% % % We need to get rid of all macros, leaving only the arguments (if present). % Of course this is not nearly correct, but it is the best we can do for now. @@ -5189,7 +5299,10 @@ end \macrolist \let\value\indexnofontsvalue } -\def\defglyph#1#2{\def#1##1{#2}} % see above + +% Give the control sequence a definition that removes the {} that follows +% its use, e.g. @AA{} -> AA +\def\indexnofontsdef#1#2{\def#1##1{#2}}% @@ -5208,6 +5321,20 @@ end \fi } +% Same as \doind, but for code indices +\def\docind#1#2{% + \iflinks + {% + % + \requireopenindexfile{#1}% + \edef\writeto{\csname#1indfile\endcsname}% + % + \def\indextext{#2}% + \safewhatsit\docindwrite + }% + \fi +} + % Check if an index file has been opened, and if not, open it. \def\requireopenindexfile#1{% \ifnum\csname #1indfile\endcsname=0 @@ -5274,6 +5401,9 @@ end % trim spaces. \edef\trimmed{\segment}% \edef\trimmed{\expandafter\eatspaces\expandafter{\trimmed}}% + \ifincodeindex + \edef\trimmed{\noexpand\code{\trimmed}}% + \fi % \xdef\bracedtext{\bracedtext{\trimmed}}% % @@ -5339,7 +5469,12 @@ end % Write the entry in \indextext to the index file. % -\def\doindwrite{% + +\newif\ifincodeindex +\def\doindwrite{\incodeindexfalse\doindwritex} +\def\docindwrite{\incodeindextrue\doindwritex} + +\def\doindwritex{% \maybemarginindex % \atdummies @@ -5559,7 +5694,11 @@ might help (with 'rm \jobname.?? \jobname.??s')% \else \begindoublecolumns \catcode`\\=0\relax - \catcode`\@=12\relax + % + % Make @ an escape character to give macros a chance to work. This + % should work because we (hopefully) don't otherwise use @ in index files. + %\catcode`\@=12\relax + \catcode`\@=0\relax \input \jobname.\indexname s \enddoublecolumns \fi @@ -6401,18 +6540,16 @@ might help (with 'rm \jobname.?? \jobname.??s')% \def\CHAPPAGoff{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chapbreak -\global\let\pagealignmacro=\chappager} +\global\def\HEADINGSon{\HEADINGSsinglechapoff}} \def\CHAPPAGon{% \global\let\contentsalignmacro = \chappager \global\let\pchapsepmacro=\chappager -\global\let\pagealignmacro=\chappager \global\def\HEADINGSon{\HEADINGSsingle}} \def\CHAPPAGodd{% \global\let\contentsalignmacro = \chapoddpage \global\let\pchapsepmacro=\chapoddpage -\global\let\pagealignmacro=\chapoddpage \global\def\HEADINGSon{\HEADINGSdouble}} \CHAPPAGon @@ -6777,9 +6914,7 @@ might help (with 'rm \jobname.?? \jobname.??s')% % \def\startcontents#1{% % If @setchapternewpage on, and @headings double, the contents should - % start on an odd page, unlike chapters. Thus, we maintain - % \contentsalignmacro in parallel with \pagealignmacro. - % From: Torbjorn Granlund + % start on an odd page, unlike chapters. \contentsalignmacro \immediate\closeout\tocfile % @@ -6794,6 +6929,9 @@ might help (with 'rm \jobname.?? \jobname.??s')% % % Roman numerals for page numbers. \ifnum \pageno>0 \global\pageno = \lastnegativepageno \fi + \def\thistitle{}% no title in double-sided headings + % Record where the Roman numerals started. + \ifnum\romancount=0 \global\romancount=\pagecount \fi } % redefined for the two-volume lispref. We always output on @@ -6816,8 +6954,7 @@ might help (with 'rm \jobname.?? \jobname.??s')% \fi \closein 1 \endgroup - \lastnegativepageno = \pageno - \global\pageno = \savepageno + \contentsendroman } % And just the chapters. @@ -6852,11 +6989,21 @@ might help (with 'rm \jobname.?? \jobname.??s')% \vfill \eject \contentsalignmacro % in case @setchapternewpage odd is in effect \endgroup - \lastnegativepageno = \pageno - \global\pageno = \savepageno + \contentsendroman } \let\shortcontents = \summarycontents +% Get ready to use Arabic numerals again +\def\contentsendroman{% + \lastnegativepageno = \pageno + \global\pageno = \savepageno + % + % If \romancount > \arabiccount, the contents are at the end of the + % document. Otherwise, advance where the Arabic numerals start for + % the page numbers. + \ifnum\romancount>\arabiccount\else\global\arabiccount=\pagecount\fi +} + % Typeset the label for a chapter or appendix for the short contents. % The arg is, e.g., `A' for an appendix, or `3' for a chapter. % @@ -7444,13 +7591,9 @@ might help (with 'rm \jobname.?? \jobname.??s')% \newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount % % We typeset each line of the verbatim in an \hbox, so we can handle -% tabs. The \global is in case the verbatim line starts with an accent, -% or some other command that starts with a begin-group. Otherwise, the -% entire \verbbox would disappear at the corresponding end-group, before -% it is typeset. Meanwhile, we can't have nested verbatim commands -% (can we?), so the \global won't be overwriting itself. +% tabs. \newbox\verbbox -\def\starttabbox{\global\setbox\verbbox=\hbox\bgroup} +\def\starttabbox{\setbox\verbbox=\hbox\bgroup} % \begingroup \catcode`\^^I=\active @@ -7461,7 +7604,8 @@ might help (with 'rm \jobname.?? \jobname.??s')% \divide\dimen\verbbox by\tabw \multiply\dimen\verbbox by\tabw % compute previous multiple of \tabw \advance\dimen\verbbox by\tabw % advance to next multiple of \tabw - \wd\verbbox=\dimen\verbbox \box\verbbox \starttabbox + \wd\verbbox=\dimen\verbbox + \leavevmode\box\verbbox \starttabbox }% } \endgroup @@ -7471,9 +7615,7 @@ might help (with 'rm \jobname.?? \jobname.??s')% \let\nonarrowing = t% \nonfillstart \tt % easiest (and conventionally used) font for verbatim - % The \leavevmode here is for blank lines. Otherwise, we would - % never \starttabbox and the \egroup would end verbatim mode. - \def\par{\leavevmode\egroup\box\verbbox\endgraf}% + \def\par{\egroup\leavevmode\box\verbbox\endgraf\starttabbox}% \tabexpand \setupmarkupstyle{verbatim}% % Respect line breaks, @@ -7481,7 +7623,6 @@ might help (with 'rm \jobname.?? \jobname.??s')% % make each space count. % Must do in this order: \obeylines \uncatcodespecials \sepspaces - \everypar{\starttabbox}% } % Do the @verb magic: verbatim text is quoted by unique @@ -7516,13 +7657,16 @@ might help (with 'rm \jobname.?? \jobname.??s')% % ignore everything up to the first ^^M, that's the newline at the end % of the @verbatim input line itself. Otherwise we get an extra blank % line in the output. - \xdef\doverbatim#1^^M#2@end verbatim{#2\noexpand\end\gobble verbatim}% + \xdef\doverbatim#1^^M#2@end verbatim{% + \starttabbox#2\egroup\noexpand\end\gobble verbatim}% % We really want {...\end verbatim} in the body of the macro, but % without the active space; thus we have to use \xdef and \gobble. + % The \egroup ends the \verbbox started at the end of the last line in + % the block. \endgroup % \envdef\verbatim{% - \setupverbatim\doverbatim + \setnormaldispenv\setupverbatim\doverbatim } \let\Everbatim = \afterenvbreak @@ -7540,7 +7684,7 @@ might help (with 'rm \jobname.?? \jobname.??s')% \wlog{texinfo.tex: doing @verbatiminclude of #1^^J}% \edef\tmp{\noexpand\input #1 } \expandafter - }\tmp + }\expandafter\starttabbox\tmp\egroup \afterenvbreak }% } @@ -10706,6 +10850,8 @@ directory should work if nowhere else does.} \DeclareUnicodeCharacter{0233}{\=y}% \DeclareUnicodeCharacter{0237}{\dotless{j}}% % + \DeclareUnicodeCharacter{02BC}{'}% + % \DeclareUnicodeCharacter{02DB}{\ogonek{ }}% % % Greek letters upper case @@ -11340,6 +11486,18 @@ directory should work if nowhere else does.} \globaldefs = 0 }} +\def\bsixpaper{{\globaldefs = 1 + \afourpaper + \internalpagesizes{140mm}{100mm}% + {-6.35mm}{-12.7mm}% + {\bindingoffset}{14pt}% + {176mm}{125mm}% + \let\SETdispenvsize=\smallword + \lispnarrowing = 0.2in + \globaldefs = 0 +}} + + % @pagesizes TEXTHEIGHT[,TEXTWIDTH] % Perhaps we should allow setting the margins, \topskip, \parskip, % and/or leading, also. Or perhaps we should compute them somehow. @@ -11353,12 +11511,12 @@ directory should work if nowhere else does.} \setleading{\textleading}% % \dimen0 = #1\relax - \advance\dimen0 by \voffset - \advance\dimen0 by 1in % reference point for DVI is 1 inch from top of page + \advance\dimen0 by 2.5in % default 1in margin above heading line + % and 1.5in to include heading, footing and + % bottom margin % \dimen2 = \hsize - \advance\dimen2 by \normaloffset - \advance\dimen2 by 1in % reference point is 1 inch from left edge of page + \advance\dimen2 by 2in % default to 1 inch margin on each side % \internalpagesizes{#1}{\hsize}% {\voffset}{\normaloffset}% diff --git a/doc/tinc-gui.8.in b/doc/tinc-gui.8.in new file mode 100644 index 0000000..e554a85 --- /dev/null +++ b/doc/tinc-gui.8.in @@ -0,0 +1,57 @@ +.Dd 2011-06-26 +.Dt TINC-GUI 8 +.\" Manual page created by: +.\" Guus Sliepen +.Sh NAME +.Nm tinc-gui +.Nd tinc GUI +.Sh SYNOPSIS +.Nm +.Op Fl n +.Op Fl -net Ns = Ns Ar NETNAME +.Op Fl -pidfile Ns = Ns Ar FILENAME +.Op Fl -help +.Sh DESCRIPTION +This is a Python/wxWidgets based graphical user interface for tinc, a secure virtual private network (VPN) project. +.Nm +communicates with +.Xr tincd 8 +to alter and inspect the running VPN's state. +It can show the current settings, the list of connections, nodes, subnets, and edges. +For now, the debug level can be changed from the GUI, and by right-clicking on a node in the list of connections, +a pop-up menu will appear that allows one to disconnect that node. +.Sh OPTIONS +.Bl -tag -width indent +.It Fl n, -net Ns = Ns Ar NETNAME +Communicate with tincd(8) connected with +.Ar NETNAME . +.It Fl -pidfile Ns = Ns Ar FILENAME +Use the cookie from +.Ar FILENAME +to authenticate with a running tinc daemon. +If unspecified, the default is +.Pa @runstatedir@/tinc. Ns Ar NETNAME Ns Pa .pid. +.It Fl -help +Display short list of options. +.El +.Sh BUGS +The GUI is not finished yet, the final version will have much more functionality. +If you find any bugs, report them to tinc@tinc-vpn.org. +.Sh SEE ALSO +.Xr tincd 8 , +.Pa http://www.tinc-vpn.org/ . +.Pp +The full documentation for tinc is maintained as a Texinfo manual. +If the info and tinc programs are properly installed at your site, +the command +.Ic info tinc +should give you access to the complete manual. +.Pp +tinc comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it under certain conditions; +see the file COPYING for details. +.Sh AUTHORS +.An "Ivo Timmermans" +.An "Guus Sliepen" Aq guus@tinc-vpn.org +.Pp +And thanks to many others for their contributions to tinc! diff --git a/doc/tinc.8.in b/doc/tinc.8.in new file mode 100644 index 0000000..ee96e86 --- /dev/null +++ b/doc/tinc.8.in @@ -0,0 +1,345 @@ +.Dd 2014-01-16 +.Dt TINCCTL 8 +.\" Manual page created by: +.\" Scott Lamb +.Sh NAME +.Nm tinc +.Nd tinc VPN control +.Sh SYNOPSIS +.Nm +.Op Fl bcn +.Op Fl -config Ns = Ns Ar DIR +.Op Fl -net Ns = Ns Ar NETNAME +.Op Fl -pidfile Ns = Ns Ar FILENAME +.Op Fl -batch +.Op Fl -force +.Op Fl -help +.Op Fl -version +.Op Ar COMMAND +.Sh DESCRIPTION +This is the control program of tinc, a secure virtual private network (VPN) +project. +.Nm +can start and stop +.Xr tincd 8 , +and can to alter and inspect the state of a running VPN. +It can also be used to change the configuration, +or to import or export host configuration files from other nodes. + +If +.Nm +is started with a +.Ar COMMAND , +this command is immediately executed, after which +.Nm +exits. +If no +.Ar COMMAND +is given, +.Nm +will act as a shell; +it will display a prompt, and commands can be entered on the prompt. +If +.Nm +is compiled with libreadline, history and command completion are available on the prompt. +One can also pipe a script containing commands through +.Nm . +In that case, lines starting with a # symbol will be ignored. +.Sh OPTIONS +.Bl -tag -width indent +.It Fl n, -net Ns = Ns Ar NETNAME +Communicate with tincd(8) connected with +.Ar NETNAME . +.It Fl -pidfile Ns = Ns Ar FILENAME +Use the cookie from +.Ar FILENAME +to authenticate with a running tinc daemon. +If unspecified, the default is +.Pa @runstatedir@/tinc. Ns Ar NETNAME Ns Pa .pid. +.It Fl b, -batch +Don't ask for anything (non-interactive mode). +.It Fl -force +Force some commands to work despite warnings. +.It Fl -help +Display short list of options. +.It Fl -version +Output version information and exit. +.El +.Sh ENVIRONMENT VARIABLES +.Bl -tag -width indent +.It Ev NETNAME +If no netname is specified on the command line with the +.Fl n +option, the value of this environment variable is used. +.El +.Sh COMMANDS +.Bl -tag -width indent +.It init Op Ar name +Create initial configuration files and RSA and Ed25519 key pairs with default length. +If no +.Ar name +for this node is given, it will be asked for. +.It get Ar variable +Print the current value of configuration variable +.Ar variable . +If more than one variable with the same name exists, +the value of each of them will be printed on a separate line. +.It set Ar variable Ar value +Set configuration variable +.Ar variable +to the given +.Ar value . +All previously existing configuration variables with the same name are removed. +To set a variable for a specific host, use the notation +.Ar host Ns Li . Ns Ar variable . +.It add Ar variable Ar value +As above, but without removing any previously existing configuration variables. +If the variable already exists with the given value, nothing happens. +.It del Ar variable Op Ar value +Remove configuration variables with the same name and +.Ar value . +If no +.Ar value +is given, all configuration variables with the same name will be removed. +.It edit Ar filename +Start an editor for the given configuration file. +You do not need to specify the full path to the file. +.It export +Export the host configuration file of the local node to standard output. +.It export-all +Export all host configuration files to standard output. +.It import +Import host configuration data generated by the +.Nm +export command from standard input. +Already existing host configuration files are not overwritten unless the option +.Fl -force +is used. +.It exchange +The same as export followed by import. +.It exchange-all +The same as export-all followed by import. +.It invite Ar name +Prepares an invitation for a new node with the given +.Ar name , +and prints a short invitation URL that can be used with the join command. +.It join Op Ar URL +Join an existing VPN using an invitation URL created using the invite command. +If no +.Ar URL +is given, it will be read from standard input. +.It start Op tincd options +Start +.Xr tincd 8 , +optionally with the given extra options. +.It stop +Stop +.Xr tincd 8 . +.It restart Op tincd options +Restart +.Xr tincd 8 , +optionally with the given extra options. +.It reload +Partially rereads configuration files. Connections to hosts whose host +config files are removed are closed. New outgoing connections specified +in +.Xr tinc.conf 5 +will be made. +.It pid +Shows the PID of the currently running +.Xr tincd 8 . +.It generate-keys Op bits +Generate both RSA and Ed25519 key pairs (see below) and exit. +.It generate-ed25519-keys +Generate public/private Ed25519 key pair and exit. +.It generate-rsa-keys Op bits +Generate public/private RSA key pair and exit. +If +.Ar bits +is omitted, the default length will be 2048 bits. +When saving keys to existing files, tinc will not delete the old keys; +you have to remove them manually. +.It dump [reachable] nodes +Dump a list of all known nodes in the VPN. +If the keyword reachable is used, only lists reachable nodes. +.It dump edges +Dump a list of all known connections in the VPN. +.It dump subnets +Dump a list of all known subnets in the VPN. +.It dump connections +Dump a list of all meta connections with ourself. +.It dump graph | digraph +Dump a graph of the VPN in +.Xr dotty 1 +format. +Nodes are colored according to their reachability: +red nodes are unreachable, orange nodes are indirectly reachable, green nodes are directly reachable. +Black nodes are either directly or indirectly reachable, but direct reachability has not been tried yet. +.It dump invitations +Dump a list of outstanding invitations. +The filename of the invitation, as well as the name of the node that is being invited is shown for each invitation. +.It info Ar node | subnet | address +Show information about a particular node, subnet or address. +If an address is given, any matching subnet will be shown. +.It purge +Purges all information remembered about unreachable nodes. +.It debug Ar N +Sets debug level to +.Ar N . +.It log Op Ar N +Capture log messages from a running tinc daemon. +An optional debug level can be given that will be applied only for log messages sent to +.Nm tinc . +.It retry +Forces +.Xr tincd 8 +to try to connect to all uplinks immediately. +Usually +.Xr tincd 8 +attempts to do this itself, +but increases the time it waits between the attempts each time it failed, +and if +.Xr tincd 8 +didn't succeed to connect to an uplink the first time after it started, +it defaults to the maximum time of 15 minutes. +.It disconnect Ar NODE +Closes the meta connection with the given +.Ar NODE . +.It top +If +.Nm +is compiled with libcurses support, this will display live traffic statistics +for all the known nodes, similar to the UNIX +.Xr top 1 +command. +See below for more information. +.It pcap +Dump VPN traffic going through the local tinc node in +.Xr pcap-savefile 5 +format to standard output, +from where it can be redirected to a file or piped through a program that can parse it directly, +such as +.Xr tcpdump 8 . +.It network Op Ar netname +If +.Ar netname +is given, switch to that network. +Otherwise, display a list of all networks for which configuration files exist. +.It fsck +This will check the configuration files for possible problems, +such as unsafe file permissions, missing executable bit on script, +unknown and obsolete configuration variables, wrong public and/or private keys, and so on. +.Pp +When problems are found, this will be printed on a line with WARNING or ERROR in front of it. +Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys), +tinc will ask if it should fix the problem. +.It sign Op Ar filename +Sign a file with the local node's private key. +If no +.Ar filename +is given, the file is read from standard input. +The signed file is written to standard output. +.It verify Ar name Op Ar filename +Check the signature of a file against a node's public key. +The +.Ar name +of the node must be given, +or can be +.Li . +to check against the local node's public key, or +.Li * +to allow a signature from any node whose public key is known. +If no +.Ar filename +is given, the file is read from standard input. +If the verification is successful, +a copy of the input with the signature removed is written to standard output, +and the exit code will be zero. +If the verification failed, +nothing will be written to standard output, and the exit code will be non-zero. +.El +.Sh EXAMPLES +Examples of some commands: +.Bd -literal -offset indent +tinc -n vpn dump graph | circo -Txlib +tinc -n vpn pcap | tcpdump -r - +tinc -n vpn top +.Pp +.Ed +Examples of changing the configuration using +.Nm : +.Bd -literal -offset indent +tinc -n vpn init foo +tinc -n vpn add Subnet 192.168.1.0/24 +tinc -n vpn add bar.Address bar.example.com +tinc -n vpn add ConnectTo bar +tinc -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@example.com +.Ed +.Sh TOP +The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters. +It displays a list of all the known nodes in the left-most column, +and the amount of bytes and packets read from and sent to each node in the other columns. +By default, the information is updated every second. +The behaviour of the top command can be changed using the following keys: +.Bl -tag +.It Ic s +Change the interval between updates. +After pressing the +.Ic s +key, enter the desired interval in seconds, followed by enter. +Fractional seconds are honored. +Intervals lower than 0.1 seconds are not allowed. +.It Ic c +Toggle between displaying current traffic rates (in packets and bytes per second) +and cumulative traffic (total packets and bytes since the tinc daemon started). +.It Ic n +Sort the list of nodes by name. +.It Ic i +Sort the list of nodes by incoming amount of bytes. +.It Ic I +Sort the list of nodes by incoming amount of packets. +.It Ic o +Sort the list of nodes by outgoing amount of bytes. +.It Ic O +Sort the list of nodes by outgoing amount of packets. +.It Ic t +Sort the list of nodes by sum of incoming and outgoing amount of bytes. +.It Ic T +Sort the list of nodes by sum of incoming and outgoing amount of packets. +.It Ic b +Show amount of traffic in bytes. +.It Ic k +Show amount of traffic in kilobytes. +.It Ic M +Show amount of traffic in megabytes. +.It Ic G +Show amount of traffic in gigabytes. +.It Ic q +Quit. +.El +.Sh BUGS +If you find any bugs, report them to tinc@tinc-vpn.org. +.Sh SEE ALSO +.Xr tincd 8 , +.Xr tinc.conf 5 , +.Xr dotty 1 , +.Xr pcap-savefile 5 , +.Xr tcpdump 8 , +.Xr top 1 , +.Pa http://www.tinc-vpn.org/ , +.Pa http://www.cabal.org/ . +.Pp +The full documentation for tinc is maintained as a Texinfo manual. +If the info and tinc programs are properly installed at your site, +the command +.Ic info tinc +should give you access to the complete manual. +.Pp +tinc comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it under certain conditions; +see the file COPYING for details. +.Sh AUTHORS +.An "Ivo Timmermans" +.An "Guus Sliepen" Aq guus@tinc-vpn.org +.Pp +And thanks to many others for their contributions to tinc! diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index cd7d1a0..7494227 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -1,4 +1,4 @@ -.Dd 2016-10-29 +.Dd 2017-09-02 .Dt TINC.CONF 5 .\" Manual page created by: .\" Ivo Timmermans @@ -11,20 +11,12 @@ The files in the .Pa @sysconfdir@/tinc/ directory contain runtime and security information for the tinc daemon. .Sh NETWORKS -It is perfectly ok for you to run more than one tinc daemon. -However, in its default form, -you will soon notice that you can't use two different configuration files without the -.Fl c -option. -.Pp -We have thought of another way of dealing with this: network names. -This means that you call -.Nm -with the +To distinguish multiple instances of tinc running on one computer, +you can use the .Fl n -option, which will assign a name to this daemon. +option to assign a network name to each tinc daemon. .Pp -The effect of this is that the daemon will set its configuration root to +The effect of this option is that the daemon will set its configuration root to .Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa / , where .Ar NETNAME @@ -32,13 +24,14 @@ is your argument to the .Fl n option. You'll notice that messages appear in syslog as coming from -.Nm tincd. Ns Ar NETNAME . +.Nm tincd. Ns Ar NETNAME , +and on Linux, unless specified otherwise, the name of the virtual network interface will be the same as the network name. .Pp -However, it is not strictly necessary that you call tinc with the +It is recommended that you use network names even if you run only one instance of tinc. +However, you can choose not to use the .Fl n option. -In this case, the network name would just be empty, -and it will be used as such. +In this case, the network name would just be empty, and .Nm tinc now looks for files in .Pa @sysconfdir@/tinc/ , @@ -48,11 +41,6 @@ the configuration file should be .Pa @sysconfdir@/tinc/tinc.conf , and the host configuration files are now expected to be in .Pa @sysconfdir@/tinc/hosts/ . -.Pp -But it is highly recommended that you use this feature of -.Nm tinc , -because it will be so much clearer whom your daemon talks to. -Hence, we will assume that you use it. .Sh NAMES Each tinc daemon must have a name that is unique in the network which it will be part of. The name will be used by other tinc daemons for identification. @@ -63,24 +51,34 @@ file. To make things easy, choose something that will give unique and easy to remember names to your tinc daemon(s). You could try things like hostnames, owner surnames or location names. +However, you are only allowed to use alphanumerical characters (a-z, A-Z, and 0-9) and underscores (_) in the name. +.Sh INITIAL CONFIGURATION +If you have not configured tinc yet, you can easily create a basic configuration using the following command: +.Bd -literal -offset indent +.Nm tinc Fl n Ar NETNAME Li init Ar NAME +.Ed +.Pp +You can further change the configuration as needed either by manually editing the configuration files, +or by using +.Xr tinc 8 . .Sh PUBLIC/PRIVATE KEYS -You should use -.Ic tincd -K -to generate public/private keypairs. -It will generate two keys. -The private key should be stored in a separate file -.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /rsa_key.priv -\-\- where -.Ar NETNAME -stands for the network (see -.Sx NETWORKS ) -above. -The public key should be stored in the host configuration file -.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Ns Va NAME -\-\- where -.Va NAME -stands for the name of the local tinc daemon (see -.Sx NAMES ) . +The +.Nm tinc Li init +command will have generated both RSA and Ed25519 public/private key pairs. +The private keys should be stored in files named +.Pa rsa_key.priv +and +.Pa ed25519_key.priv +in the directory +.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa / +The public keys should be stored in the host configuration file +.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Ns Va NAME . +The RSA keys are used for backwards compatibility with tinc version 1.0. +If you are upgrading from version 1.0 to 1.1, you can keep the old configuration files, +but you will need to create Ed25519 keys using the following command: +.Bd -literal -offset indent +.Nm tinc Fl n Ar NETNAME Li generate-ed25519-keys +.Ed .Sh SERVER CONFIGURATION The server configuration of the daemon is done in the file .Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /tinc.conf . @@ -103,6 +101,10 @@ Although all configuration options for the local host listed in this document ca it is recommended to put host specific configuration options in the host configuration file, as this makes it easy to exchange with other nodes. .Pp +You can edit the config file manually, but it is recommended that you use +.Xr tinc 8 +to change configuration variables for you. +.Pp Here are all valid variables, listed in alphabetical order. The default value is given between parentheses. .Bl -tag -width indent @@ -112,26 +114,24 @@ If .Qq any is selected, then depending on the operating system both IPv4 and IPv6 or just IPv6 listening sockets will be created. -.It Va BindToAddress Li = Ar address Oo Ar port Oc Bq experimental -If your computer has more than one IPv4 or IPv6 address, +.It Va AutoConnect Li = yes | no Pq yes +If set to yes, .Nm tinc -will by default listen on all of them for incoming connections. -Multiple +will automatically set up meta connections to other nodes, +without requiring +.Va ConnectTo +variables. +.Pp +Note: it is not possible to connect to nodes using zero (system-assigned) ports in this way. +.It Va BindToAddress Li = Ar address Op Ar port +This is the same as +.Va ListenAddress , +however the address given with the .Va BindToAddress -variables may be specified, -in which case listening sockets for each specified address are made. -.Pp -If no -.Ar port -is specified, the socket will be bound to the port specified by the -.Va Port -option, or to port 655 if neither is given. -To only bind to a specific port but not to a specific address, use -.Li * -for the -.Ar address . -.Pp -This option may not work on all platforms. +option will also be used for outgoing connections. This is useful if your +computer has more than one IPv4 or IPv6 address, and you want +.Nm tinc +to only use a specific one for outgoing packets. .It Va BindToInterface Li = Ar interface Bq experimental If your computer has more than one network interface, .Nm tinc @@ -157,6 +157,13 @@ Broadcast packets are sent directly to all nodes that can be reached directly. Broadcast packets received from other nodes are never forwarded. If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. .El +.It Va BroadcastSubnet Li = Ar address Ns Op Li / Ns Ar prefixlength +Declares a broadcast subnet. Any packet with a destination address falling into such a subnet will be routed as a broadcast (provided all nodes have it declared). +This is most useful to declare subnet broadcast addresses (e.g. 10.42.255.255), otherwise +.Nm tinc +won't know what to do with them. +.Pp +Note that global broadcast addresses (MAC ff:ff:ff:ff:ff:ff, IPv4 255.255.255.255), as well as multicast space (IPv4 224.0.0.0/4, IPv6 ff00::/8) are always considered broadcast addresses and don't need to be declared. .It Va ConnectTo Li = Ar name Specifies which other tinc daemon to connect to on startup. Multiple @@ -169,7 +176,9 @@ The names should be known to this tinc daemon line). .Pp If you don't specify a host with -.Va ConnectTo , +.Va ConnectTo +and have disabled +.Va AutoConnect , .Nm tinc won't try to connect to other daemons at all, and will instead just listen for incoming connections. @@ -193,6 +202,13 @@ instead of .Va Device . The info pages of the tinc package contain more information about configuring the virtual network device. +.It Va DeviceStandby Li = yes | no Po no Pc +When disabled, +.Nm tinc +calls tinc-up on startup, and tinc-down on shutdown. When enabled, +.Nm tinc +will only call tinc-up when at least one node is reachable, and will call tinc-down as soon as no nodes are reachable. +On Windows, this also determines when the virtual network interface "cable" is "plugged". .It Va DeviceType Li = Ar type Pq platform dependent The type of the virtual network device. Tinc will normally automatically select the right type of tun/tap interface, and this option should not be used. @@ -218,6 +234,11 @@ Do NOT connect multiple .Nm tinc daemons to the same multicast address, this will very likely cause routing loops. Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. +.It fd +Use a file descriptor, given directly as an integer or passed through a unix domain socket. +On Linux, an abstract socket address can be specified by using "@" as a prefix. +All packets are read from this interface. +Packets received for the local node are written to it. .It uml Pq not compiled in by default Create a UNIX socket with the filename specified by .Va Device , @@ -264,6 +285,17 @@ When this option is enabled, packets that cannot be sent directly to the destina but which would have to be forwarded by an intermediate node, are dropped instead. When combined with the IndirectData option, packets for nodes for which we do not have a meta connection with are also dropped. +.It Va Ed25519PrivateKeyFile Li = Ar filename Po Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /ed25519_key.priv Pc +The file in which the private Ed25519 key of this tinc daemon resides. +This is only used if +.Va ExperimentalProtocol +is enabled. +.It Va ExperimentalProtocol Li = yes | no Pq yes +When this option is enabled, the SPTPS protocol will be used when connecting to nodes that also support it. +Ephemeral ECDH will be used for key exchanges, +and Ed25519 will be used instead of RSA for authentication. +When enabled, an Ed25519 key must have been generated before with +.Nm tinc generate-ed25519-keys . .It Va Forwarding Li = off | internal | kernel Po internal Pc Bq experimental This option selects the way indirect packets are forwarded. .Bl -tag -width indent @@ -275,22 +307,16 @@ Incoming packets that are meant for another node are forwarded by tinc internall .Pp This is the default mode, and unless you really know you need another forwarding mode, don't change it. .It kernel -Incoming packets are always sent to the TUN/TAP device, even if the packets are not for the local node. +Incoming packets using the legacy protocol are always sent to the TUN/TAP device, +even if the packets are not for the local node. This is less efficient, but allows the kernel to apply its routing and firewall rules on them, and can also help debugging. +Incoming packets using the SPTPS protocol are dropped, since they are end-to-end encrypted. .El -.It Va GraphDumpFile Li = Ar filename Bq experimental -If this option is present, -.Nm tinc -will dump the current network graph to the file -.Ar filename -every minute, unless there were no changes to the graph. -The file is in a format that can be read by graphviz tools. -If -.Ar filename -starts with a pipe symbol |, -then the rest of the filename is interpreted as a shell command -that is executed, the graph is then sent to stdin. +.It Va FWMark Li = Ar value Po 0 Pc Bq experimental +When set to a non-zero value, all TCP and UDP sockets created by tinc will use the given value as the firewall mark. +This can be used for mark-based routing or for packet filtering. +This option is currently only supported on Linux. .It Va Hostnames Li = yes | no Pq no This option selects whether IP addresses (both real and on the VPN) should be resolved. Since DNS lookups are blocking, it might affect tinc's @@ -308,11 +334,40 @@ Under Windows, this variable is used to select which network interface will be u If you specified a .Va Device , this variable is almost always already correctly set. +.It Va InvitationExpire Li = Ar seconds Pq 604800 +This option controls the period invitations are valid. .It Va KeyExpire Li = Ar seconds Pq 3600 This option controls the period the encryption keys used to encrypt the data are valid. It is common practice to change keys at regular intervals to make it even harder for crackers, even though it is thought to be nearly impossible to crack a single key. -.It Va LocalDiscovery Li = yes | no Po no Pc Bq experimental +.It Va ListenAddress Li = Ar address Op Ar port +If your computer has more than one IPv4 or IPv6 address, +.Nm tinc +will by default listen on all of them for incoming connections. +This option can be used to restrict which addresses tinc listens on. +Multiple +.Va ListenAddress +variables may be specified, +in which case listening sockets for each specified address are made. +.Pp +If no +.Ar port +is specified, the socket will listen on the port specified by the +.Va Port +option, or to port 655 if neither is given. +To only listen on a specific port but not on a specific address, use +.Li * +for the +.Ar address . +.Pp +If +.Ar port +is set to zero, it will be randomly assigned by the system. This is useful to randomize source ports of UDP packets, which can improve UDP hole punching reliability. In this case it is recommended to set +.Va AddressFamily +as well, otherwise +.Nm tinc +will assign different ports to different address families but other nodes can only know of one. +.It Va LocalDiscovery Li = yes | no Pq yes When enabled, .Nm tinc will try to detect peers that are on the same local network. @@ -320,14 +375,20 @@ This will allow direct communication using LAN addresses, even if both peers are and they only ConnectTo a third node outside the NAT, which normally would prevent the peers from learning each other's LAN address. .Pp -Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery. -This feature may not work in all possible situations. +Currently, local discovery is implemented by sending some packets to the local address of the node during UDP discovery. This will not work with old nodes that don't transmit their local address. +.It Va LogLevel Li = level Pq 0 +This option controls the verbosity of the logging. The higher the debug level, the more messages it will log. .It Va MACExpire Li = Ar seconds Pq 600 This option controls the amount of time MAC addresses are kept before they are removed. This only has effect when .Va Mode is set to .Qq switch . +.It Va MaxConnectionBurst Li = Ar count Pq 100 +This option controls how many connections tinc accepts in quick succession. +If there are more connections than the given number in a short time interval, +tinc will reduce the number of accepted connections to only one per second, +until the burst has passed. .It Va MaxTimeout Li = Ar seconds Pq 900 This is the maximum delay before trying to reconnect to other tinc daemons. .It Va Mode Li = router | switch | hub Pq router @@ -337,7 +398,7 @@ This option selects the way packets are routed to other daemons. In this mode .Va Subnet variables in the host configuration files will be used to form a routing table. -Only unicast packets of routable protocols (IPv4 and IPv6) are supported in this mode. +Only packets of routable protocols (IPv4 and IPv6) are supported in this mode. .Pp This is the default mode, and unless you really know you need another mode, don't change it. .It switch @@ -355,7 +416,8 @@ while no routing table is managed. .It Va Name Li = Ar name Bq required This is the name which identifies this tinc daemon. It must be unique for the virtual private network this daemon will connect to. -The Name may only consist of alphanumeric and underscore characters. +.Va Name +may only consist of alphanumeric and underscore characters (a-z, A-Z, 0-9 and _), and is case sensitive. If .Va Name starts with a @@ -385,7 +447,9 @@ It will allow this tinc daemon to authenticate itself to other daemons. .It Va PrivateKeyFile Li = Ar filename Po Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /rsa_key.priv Pc The file in which the private RSA key of this tinc daemon resides. .It Va ProcessPriority Li = low | normal | high -When this option is used the priority of the tincd process will be adjusted. +When this option is used the priority of the +.Nm tincd +process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. .It Va Proxy Li = socks4 | socks5 | http | exec Ar ... Bq experimental Use a proxy when making outgoing connections. @@ -419,13 +483,13 @@ and .Ev REMOTEPORT are available. .El -.It Va ReplayWindow Li = Ar bytes Pq 16 +.It Va ReplayWindow Li = Ar bytes Pq 32 This is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet per bit, so for example -the default setting of 16 will track up to 128 packets in the window. In high +the default setting of 32 will track up to 256 packets in the window. In high bandwidth scenarios, setting this to a higher value can reduce packet loss from the interaction of replay tracking with underlying real packet loss and/or -reordering. Setting this to zero will disable replay tracking completely and +reordering. Setting this to zero will disable replay tracking completely and pass all traffic, but leaves tinc vulnerable to replay-based attacks on your traffic. .It Va StrictSubnets Li = yes | no Po no Pc Bq experimental @@ -440,12 +504,42 @@ and will only allow connections with nodes for which host config files are prese .Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ directory. Setting this options also implicitly sets StrictSubnets. -.It Va UDPRcvBuf Li = Ar bytes Pq OS default +.It Va UDPDiscovery Li = yes | no Po yes Pc +When this option is enabled tinc will try to establish UDP connectivity to nodes, +using TCP while it determines if a node is reachable over UDP. If it is disabled, +tinc always assumes a node is reachable over UDP. +Note that tinc will never use UDP with nodes that have +.Va TCPOnly +enabled. +.It Va UDPDiscoveryKeepaliveInterval Li = Ar seconds Pq 9 +The minimum amount of time between sending UDP ping datagrams to check UDP connectivity once it has been established. +Note that these pings are large, since they are used to verify link MTU as well. +.It Va UDPDiscoveryInterval Li = Ar seconds Pq 2 +The minimum amount of time between sending UDP ping datagrams to try to establish UDP connectivity. +.It Va UDPDiscoveryTimeout Li = Ar seconds Pq 30 +If tinc doesn't receive any UDP ping replies over the specified interval, +it will assume UDP communication is broken and will fall back to TCP. +.It Va UDPInfoInterval Li = Ar seconds Pq 5 +The minimum amount of time between sending periodic updates about UDP addresses, which are mostly useful for UDP hole punching. +.It Va UDPRcvBuf Li = Ar bytes Pq 1048576 Sets the socket receive buffer size for the UDP socket, in bytes. -If unset, the default buffer size will be used by the operating system. -.It Va UDPSndBuf Li = Ar bytes Pq OS default +If set to zero, the default buffer size will be used by the operating system. +Note: this setting can have a significant impact on performance, especially raw throughput. +.It Va UDPSndBuf Li = Ar bytes Pq 1048576 Sets the socket send buffer size for the UDP socket, in bytes. -If unset, the default buffer size will be used by the operating system. +If set to zero, the default buffer size will be used by the operating system. +Note: this setting can have a significant impact on performance, especially raw throughput. +.It Va UPnP Li = yes | udponly | no Po no Pc +If this option is enabled then tinc will search for UPnP-IGD devices on the local network. +It will then create and maintain port mappings for tinc's listening TCP and UDP ports. +If set to "udponly", tinc will only create a mapping for its UDP (data) port, not for its TCP (metaconnection) port. +Note that tinc must have been built with miniupnpc support for this feature to be available. +Furthermore, be advised that enabling this can have security implications, because the miniupnpc library that +tinc uses might not be well-hardened with regard to malicious UPnP replies. +.It Va UPnPDiscoverWait Li = Ar seconds Pq 5 +The amount of time to wait for replies when probing the local network for UPnP devices. +.It Va UPnPRefreshPeriod Li = Ar seconds Pq 60 +How often tinc will re-add the port mapping, in case it gets reset on the UPnP device. This also controls the duration of the port mapping itself, which will be set to twice that duration. .El .Sh HOST CONFIGURATION FILES The host configuration files contain all information needed @@ -468,13 +562,15 @@ Multiple .Va Address variables can be specified, in which case each address will be tried until a working connection has been established. -.It Va Cipher Li = Ar cipher Pq aes-256-cbc +.It Va Cipher Li = Ar cipher Pq blowfish The symmetric cipher algorithm used to encrypt UDP packets. Any cipher supported by LibreSSL or OpenSSL is recognised. Furthermore, specifying .Qq none will turn off packet encryption. It is best to use only those ciphers which support CBC mode. +This option has no effect for connections between nodes using +.Va ExperimentalProtocol . .It Va ClampMSS Li = yes | no Pq yes This option specifies whether tinc should clamp the maximum segment size (MSS) of TCP packets to the path MTU. This helps in situations where ICMP @@ -483,12 +579,14 @@ Fragmentation Needed or Packet too Big messages are dropped by firewalls. This option sets the level of compression used for UDP packets. Possible values are 0 (off), 1 (fast zlib) and any integer up to 9 (best zlib), 10 (fast lzo) and 11 (best lzo). -.It Va Digest Li = Ar digest Pq sha256 +.It Va Digest Li = Ar digest Pq sha1 The digest algorithm used to authenticate UDP packets. Any digest supported by LibreSSL or OpenSSL is recognised. Furthermore, specifying .Qq none will turn off packet authentication. +This option has no effect for connections between nodes using +.Va ExperimentalProtocol . .It Va IndirectData Li = yes | no Pq no When set to yes, only nodes which already have a meta connection to you will try to establish direct communication with you. @@ -498,16 +596,28 @@ The length of the message authentication code used to authenticate UDP packets. Can be anything from .Qq 0 up to the length of the digest produced by the digest algorithm. +This option has no effect for connections between nodes using +.Va ExperimentalProtocol . .It Va PMTU Li = Ar mtu Po 1514 Pc This option controls the initial path MTU to this node. .It Va PMTUDiscovery Li = yes | no Po yes Pc When this option is enabled, tinc will try to discover the path MTU to this node. After the path MTU has been discovered, it will be enforced on the VPN. +.It Va MTUInfoInterval Li = Ar seconds Pq 5 +The minimum amount of time between sending periodic updates about relay path MTU. Useful for quickly determining MTU to indirect nodes. .It Va Port Li = Ar port Pq 655 The port number on which this tinc daemon is listening for incoming connections, which is used if no port number is specified in an .Va Address statement. +.Pp +If this is set to zero, the port will be randomly assigned by the system. This is useful to randomize source ports of UDP packets, which can improve UDP hole punching reliability. When setting +.Va Port +to zero it is recommended to set +.Va AddressFamily +as well, otherwise +.Nm tinc +will assign different ports to different address families but other nodes can only know of one. .It Va PublicKey Li = Ar key Bq obsolete The public RSA key of this tinc daemon. It will be used to cryptographically verify it's identity and to set up a secure connection. @@ -542,7 +652,7 @@ IPv6 subnets are notated like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e. .Pp A Subnet can be given a weight to indicate its priority over identical Subnets -owned by different nodes. The default weight is 10. Lower values indicate +owned by different nodes. The default weight is 10. Lower values indicate higher priority. Packets will be sent to the node with the highest priority, unless that node is not reachable, in which case the node with the next highest priority will be tried, and so on. @@ -556,6 +666,12 @@ Setting this options also implicitly sets IndirectData. .Pp Since version 1.0.10, tinc will automatically detect whether communication via UDP is possible or not. +.It Va Weight Li = Ar weight +If this variable is set, it overrides the weight given to connections made with +another host. A higher +.Ar weight +means a lower priority is given to this connection when broadcasting or +forwarding packets. .El .Sh SCRIPTS Apart from reading the server and host configuration files, @@ -568,18 +684,24 @@ this means that tinc will temporarily stop processing packets until the called s This guarantees that scripts will execute in the exact same order as the events that trigger them. If you need to run commands asynchronously, you have to ensure yourself that they are being run in the background. .Pp -Under Windows (not Cygwin), the scripts must have the extension -.Pa .bat . +Under Windows, the scripts must have the extension +.Pa .bat +or +.Pa .cmd . .Bl -tag -width indent .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /tinc-up This is the most important script. -If it is present it will be executed right after the tinc daemon has been started and has connected to the virtual network device. +If it is present it will be executed right after the tinc daemon has been started and has connected to the virtual network device (or when the first node becomes reachable if +.Va DeviceStandby +is used). It should be used to set up the corresponding network interface, but can also be used to start other things. .Pp Under Windows you can use the Network Connections control panel instead of creating this script. .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /tinc-down -This script is started right before the tinc daemon quits. +This script is started right before the tinc daemon quits (or when the last node becomes unreachable if +.Va DeviceStandby +is used). .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Ns Ar HOST Ns Pa -up This script is started when the tinc daemon with name .Ar HOST @@ -597,6 +719,10 @@ This script is started when a Subnet becomes reachable. The Subnet and the node it belongs to are passed in environment variables. .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /subnet-down This script is started when a Subnet becomes unreachable. +.It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /invitation-created +This script is started when a new invitation has been created. +.It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /invitation-accepted +This script is started when an invitation has been used. .El .Pp The scripts are started without command line arguments, but can make use of certain environment variables. @@ -605,6 +731,8 @@ Under UNIX like operating systems the names of environment variables must be pre in scripts. Under Windows, in .Pa .bat +or +.Pa .cmd files, they have to be put between .Li % signs. @@ -630,6 +758,14 @@ When a host becomes (un)reachable, this is set to the port number it uses for co When a subnet becomes (un)reachable, this is set to the subnet. .It Ev WEIGHT When a subnet becomes (un)reachable, this is set to the subnet weight. +.It Ev INVITATION_FILE +When the +.Pa invitation-created +script is called, this is set to the file where the invitation details will be stored. +.It Ev INVITATION_URL +When the +.Pa invitation-created +script is called, this is set to the invitation URL that has been created. .El .Pp Do not forget that under UNIX operating systems, you have to make the scripts executable, using the command @@ -643,7 +779,7 @@ The top directory for configuration files. The default name of the server configuration file for net .Ar NETNAME . .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /conf.d/ -Optional directory from which any *.conf file will be loaded +Optional directory from which any .conf file will be loaded .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Host configuration files are kept in this directory. .It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /tinc-up @@ -654,9 +790,14 @@ It can be used to set up the corresponding network interface. If an executable file with this name exists, it will be executed right before the tinc daemon is going to close its connection to the virtual network device. +.It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /invitations/ +This directory contains outstanding invitations. +.It Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /invitation-data +After a successful join, this file contains a copy of the invitation data received. .El .Sh SEE ALSO .Xr tincd 8 , +.Xr tinc 8 , .Pa https://www.tinc-vpn.org/ , .Pa http://www.tldp.org/LDP/nag2/ . .Pp diff --git a/doc/tinc.info b/doc/tinc.info index 7ee4d91..4dc9a0b 100644 --- a/doc/tinc.info +++ b/doc/tinc.info @@ -5,10 +5,10 @@ START-INFO-DIR-ENTRY * tinc: (tinc). The tinc Manual. END-INFO-DIR-ENTRY -This is the info manual for tinc version 1.0.36, a Virtual Private -Network daemon. +This is the info manual for tinc version 1.1pre17-49-g4cc4b9bc, a +Virtual Private Network daemon. - Copyright (C) 1998-2019 Ivo Timmermans, Guus Sliepen + Copyright (C) 1998-2021 Ivo Timmermans, Guus Sliepen and Wessel Dankers . Permission is granted to make and distribute verbatim copies of this @@ -33,6 +33,8 @@ Top * Installation:: * Configuration:: * Running tinc:: +* Controlling tinc:: +* Invitations:: * Technical information:: * Platform specific information:: * About us:: @@ -48,13 +50,13 @@ Tinc is a Virtual Private Network (VPN) daemon that uses tunneling and encryption to create a secure private network between hosts on the Internet. - Because the tunnel appears to the IP level network code as a normal +Because the tunnel appears to the IP level network code as a normal network device, there is no need to adapt any existing software. The encrypted tunnels allows VPN sites to share information with each other over the Internet without exposing any information to others. - This document is the manual for tinc. Included are chapters on how -to configure your computer to use tinc, as well as the configuration +This document is the manual for tinc. Included are chapters on how to +configure your computer to use tinc, as well as the configuration process of tinc itself. * Menu: @@ -73,7 +75,7 @@ A Virtual Private Network or VPN is a network that can only be accessed by a few elected computers that participate. This goal is achievable in more than just one way. - Private networks can consist of a single stand-alone Ethernet LAN. Or +Private networks can consist of a single stand-alone Ethernet LAN. Or even two computers hooked up using a null-modem cable. In these cases, it is obvious that the network is _private_, no one can access it from the outside. But if your computers are linked to the Internet, the @@ -81,7 +83,7 @@ network is not private anymore, unless one uses firewalls to block all private traffic. But then, there is no way to send private data to trusted computers on the other end of the Internet. - This problem can be solved by using _virtual_ networks. Virtual +This problem can be solved by using _virtual_ networks. Virtual networks can live on top of other networks, but they use encapsulation to keep using their private address space so they do not interfere with the Internet. Mostly, virtual networks appear like a single LAN, even @@ -89,14 +91,14 @@ though they can span the entire world. But virtual networks can't be secured by using firewalls, because the traffic that flows through it has to go through the Internet, where other people can look at it. - As is the case with either type of VPN, anybody could eavesdrop. Or +As is the case with either type of VPN, anybody could eavesdrop. Or worse, alter data. Hence it's probably advisable to encrypt the data that flows over the network. - When one introduces encryption, we can form a true VPN. Other people -may see encrypted traffic, but if they don't know how to decipher it -(they need to know the key for that), they cannot read the information -that flows through the VPN. This is what tinc was made for. +When one introduces encryption, we can form a true VPN. Other people may +see encrypted traffic, but if they don't know how to decipher it (they +need to know the key for that), they cannot read the information that +flows through the VPN. This is what tinc was made for.  File: tinc.info, Node: tinc, Next: Supported platforms, Prev: Virtual Private Networks, Up: Introduction @@ -110,26 +112,26 @@ used the ethertap device that Linux knows of since somewhere about kernel 2.1.60. It didn't work immediately and he improved it a bit. At this stage, the project was still simply called "vpnd". - Since then, a lot has changed--to say the least. +Since then, a lot has changed--to say the least. - Tinc now supports encryption, it consists of a single daemon (tincd) -for both the receiving and sending end, it has become largely +Tinc now supports encryption, it consists of a single daemon (tincd) for +both the receiving and sending end, it has become largely runtime-configurable--in short, it has become a full-fledged professional package. - Tinc also allows more than two sites to connect to each other and -form a single VPN. Traditionally VPNs are created by making tunnels, -which only have two endpoints. Larger VPNs with more sites are created -by adding more tunnels. Tinc takes another approach: only endpoints are +Tinc also allows more than two sites to connect to each other and form a +single VPN. Traditionally VPNs are created by making tunnels, which only +have two endpoints. Larger VPNs with more sites are created by adding +more tunnels. Tinc takes another approach: only endpoints are specified, the software itself will take care of creating the tunnels. This allows for easier configuration and improved scalability. - A lot can--and will be--changed. We have a number of things that we +A lot can--and will be--changed. We have a number of things that we would like to see in the future releases of tinc. Not everything will be available in the near future. Our first objective is to make tinc work perfectly as it stands, and then add more advanced features. - Meanwhile, we're always open-minded towards new ideas. And we're +Meanwhile, we're always open-minded towards new ideas. And we're available too.  @@ -139,15 +141,14 @@ File: tinc.info, Node: Supported platforms, Prev: tinc, Up: Introduction ======================= Tinc has been verified to work under Linux, FreeBSD, OpenBSD, NetBSD, -Mac OS X (Darwin), Solaris, and Windows (both natively and in a Cygwin -environment), with various hardware architectures. These are some of -the platforms that are supported by the universal tun/tap device driver -or other virtual network device drivers. Without such a driver, tinc -will most likely compile and run, but it will not be able to send or -receive data packets. +MacOS/X (Darwin), Solaris, and Windows, with various hardware +architectures. These are some of the platforms that are supported by +the universal tun/tap device driver or other virtual network device +drivers. Without such a driver, tinc will most likely compile and run, +but it will not be able to send or receive data packets. - For an up to date list of supported platforms, please check the list -on our website: . +For an up to date list of supported platforms, please check the list on +our website: .  File: tinc.info, Node: Preparations, Next: Installation, Prev: Introduction, Up: Top @@ -176,7 +177,7 @@ File: tinc.info, Node: Configuring the kernel, Next: Libraries, Up: Preparati * Configuration of OpenBSD kernels:: * Configuration of NetBSD kernels:: * Configuration of Solaris kernels:: -* Configuration of Darwin (Mac OS X) kernels:: +* Configuration of Darwin (MacOS/X) kernels:: * Configuration of Windows::  @@ -194,11 +195,11 @@ Here are the options you have to turn on when configuring a new kernel: Network device support Universal tun/tap device driver support - It's not necessary to compile this driver as a module, even if you -are going to run more than one instance of tinc. +It's not necessary to compile this driver as a module, even if you are +going to run more than one instance of tinc. - If you decide to build the tun/tap driver as a kernel module, add -these lines to '/etc/modules.conf': +If you decide to build the tun/tap driver as a kernel module, add these +lines to '/etc/modules.conf': alias char-major-10-200 tun @@ -231,10 +232,10 @@ File: tinc.info, Node: Configuration of NetBSD kernels, Next: Configuration of For NetBSD version 1.5.2 and higher, the tun driver is included in the default kernel configuration. - Tunneling IPv6 may not work on NetBSD's tun device. +Tunneling IPv6 may not work on NetBSD's tun device.  -File: tinc.info, Node: Configuration of Solaris kernels, Next: Configuration of Darwin (Mac OS X) kernels, Prev: Configuration of NetBSD kernels, Up: Configuring the kernel +File: tinc.info, Node: Configuration of Solaris kernels, Next: Configuration of Darwin (MacOS/X) kernels, Prev: Configuration of NetBSD kernels, Up: Configuring the kernel 2.1.5 Configuration of Solaris kernels -------------------------------------- @@ -247,24 +248,24 @@ sparc64 architectures, precompiled versions can be found at header file is missing, install it from the source package.  -File: tinc.info, Node: Configuration of Darwin (Mac OS X) kernels, Next: Configuration of Windows, Prev: Configuration of Solaris kernels, Up: Configuring the kernel +File: tinc.info, Node: Configuration of Darwin (MacOS/X) kernels, Next: Configuration of Windows, Prev: Configuration of Solaris kernels, Up: Configuring the kernel -2.1.6 Configuration of Darwin (Mac OS X) kernels ------------------------------------------------- +2.1.6 Configuration of Darwin (MacOS/X) kernels +----------------------------------------------- Tinc on Darwin relies on a tunnel driver for its data acquisition from the kernel. OS X version 10.6.8 and later have a built-in tun driver called "utun". Tinc also supports the driver from , which supports both tun and tap -style devices. +style devices, - By default, tinc expects the tuntaposx driver to be installed. To -use the utun driver, set add 'Device = utunX' to 'tinc.conf', where X is -the desired number for the utun interface. You can also omit the -number, in which case the first free number will be chosen. +By default, tinc expects the tuntaposx driver to be installed. To use +the utun driver, set add 'Device = utunX' to 'tinc.conf', where X is the +desired number for the utun interface. You can also omit the number, in +which case the first free number will be chosen.  -File: tinc.info, Node: Configuration of Windows, Prev: Configuration of Darwin (Mac OS X) kernels, Up: Configuring the kernel +File: tinc.info, Node: Configuration of Windows, Prev: Configuration of Darwin (MacOS/X) kernels, Up: Configuring the kernel 2.1.7 Configuration of Windows ------------------------------ @@ -283,15 +284,17 @@ File: tinc.info, Node: Libraries, Prev: Configuring the kernel, Up: Preparati ============= Before you can configure or build tinc, you need to have the LibreSSL or -OpenSSL, zlib and lzo libraries installed on your system. If you try to -configure tinc without having them installed, configure will give you an -error message, and stop. +OpenSSL, zlib, LZO, curses and readline libraries installed on your +system. If you try to configure tinc without having them installed, +configure will give you an error message, and stop. * Menu: * LibreSSL/OpenSSL:: * zlib:: -* lzo:: +* LZO:: +* libcurses:: +* libreadline::  File: tinc.info, Node: LibreSSL/OpenSSL, Next: zlib, Up: Libraries @@ -302,25 +305,25 @@ File: tinc.info, Node: LibreSSL/OpenSSL, Next: zlib, Up: Libraries For all cryptography-related functions, tinc uses the functions provided by the LibreSSL or the OpenSSL library. - If this library is not installed, you will get an error when -configuring tinc for build. Support for running tinc with other -cryptographic libraries installed _may_ be added in the future. +If this library is not installed, you will get an error when configuring +tinc for build. Support for running tinc with other cryptographic +libraries installed _may_ be added in the future. - You can use your operating system's package manager to install this -if available. Make sure you install the development AND runtime -versions of this package. +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. - If your operating system comes neither with LibreSSL or OpenSSL, you +If your operating system comes neither with LibreSSL or OpenSSL, you have to install one manually. It is recommended that you get the latest -version of LibreSSL from . Instructions on +version of LibreSSL from . Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default). - If you installed the LibreSSL or OpenSSL libraries from source, it -may be necessary to let configure know where they are, by passing -configure one of the -with-openssl-* parameters. Note that you even -have to use -with-openssl-* if you are using LibreSSL. +If you installed the LibreSSL or OpenSSL libraries from source, it may +be necessary to let configure know where they are, by passing configure +one of the -with-openssl-* parameters. Note that you even have to use +-with-openssl-* if you are using LibreSSL. --with-openssl=DIR LibreSSL/OpenSSL library and headers prefix --with-openssl-include=DIR LibreSSL/OpenSSL headers directory @@ -343,8 +346,8 @@ everyone to create a statically or dynamically linked executable: You may provide binary packages linked to the OpenSSL libraries, provided that all other requirements of the GPL are met. - Since the LZO library used by tinc is also covered by the GPL, we -also present the following exemption: +Since the LZO library used by tinc is also covered by the GPL, we also +present the following exemption: Hereby I grant a special exception to the tinc VPN project (https://www.tinc-vpn.org/) to link the LZO library with the @@ -353,7 +356,7 @@ also present the following exemption: Markus F.X.J. Oberhumer  -File: tinc.info, Node: zlib, Next: lzo, Prev: LibreSSL/OpenSSL, Up: Libraries +File: tinc.info, Node: zlib, Next: LZO, Prev: LibreSSL/OpenSSL, Up: Libraries 2.2.2 zlib ---------- @@ -361,47 +364,94 @@ File: tinc.info, Node: zlib, Next: lzo, Prev: LibreSSL/OpenSSL, Up: Librarie For the optional compression of UDP packets, tinc uses the functions provided by the zlib library. - If this library is not installed, you will get an error when running -the configure script. You can either install the zlib library, or -disable support for zlib compression by using the "-disable-zlib" option -when running the configure script. Note that if you disable support for +If this library is not installed, you will get an error when running the +configure script. You can either install the zlib library, or disable +support for zlib compression by using the '--disable-zlib' option when +running the configure script. Note that if you disable support for zlib, the resulting binary will not work correctly on VPNs where zlib compression is used. - You can use your operating system's package manager to install this -if available. Make sure you install the development AND runtime -versions of this package. +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. - If you have to install zlib manually, you can get the source code -from . Instructions on how to configure, build and +If you have to install zlib manually, you can get the source code from +. Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default).  -File: tinc.info, Node: lzo, Prev: zlib, Up: Libraries +File: tinc.info, Node: LZO, Next: libcurses, Prev: zlib, Up: Libraries -2.2.3 lzo +2.2.3 LZO --------- Another form of compression is offered using the LZO library. - If this library is not installed, you will get an error when running -the configure script. You can either install the LZO library, or -disable support for LZO compression by using the "-disable-lzo" option -when running the configure script. Note that if you disable support for -LZO, the resulting binary will not work correctly on VPNs where LZO +If this library is not installed, you will get an error when running the +configure script. You can either install the LZO library, or disable +support for LZO compression by using the '--disable-lzo' option when +running the configure script. Note that if you disable support for LZO, +the resulting binary will not work correctly on VPNs where LZO compression is used. - You can use your operating system's package manager to install this -if available. Make sure you install the development AND runtime -versions of this package. +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. - If you have to install lzo manually, you can get the source code from +If you have to install LZO manually, you can get the source code from . Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default). + +File: tinc.info, Node: libcurses, Next: libreadline, Prev: LZO, Up: Libraries + +2.2.4 libcurses +--------------- + +For the 'tinc top' command, tinc requires a curses library. + +If this library is not installed, you will get an error when running the +configure script. You can either install a suitable curses library, or +disable all functionality that depends on a curses library by using the +'--disable-curses' option when running the configure script. + +There are several curses libraries. It is recommended that you install +"ncurses" (), however other +curses libraries should also work. In particular, "PDCurses" +() is recommended if you want to +compile tinc for Windows. + +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. + + +File: tinc.info, Node: libreadline, Prev: libcurses, Up: Libraries + +2.2.5 libreadline +----------------- + +For the 'tinc' command's shell functionality, tinc uses the readline +library. + +If this library is not installed, you will get an error when running the +configure script. You can either install a suitable readline library, +or disable all functionality that depends on a readline library by using +the '--disable-readline' option when running the configure script. + +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. + +If you have to install libreadline manually, you can get the source code +from . Instructions on how to +configure, build and install this package are included within the +package. Please make sure you build development and runtime libraries +(which is the default). +  File: tinc.info, Node: Installation, Next: Configuration, Prev: Preparations, Up: Top @@ -412,13 +462,13 @@ If you use Debian, you may want to install one of the precompiled packages for your system. These packages are equipped with system startup scripts and sample configurations. - If you cannot use one of the precompiled packages, or you want to +If you cannot use one of the precompiled packages, or you want to compile tinc for yourself, you can use the source. The source is distributed under the GNU General Public License (GPL). Download the source from the download page (https://www.tinc-vpn.org/download/). - Tinc comes in a convenient autoconf/automake package, which you can -just treat the same as any other package. Which is just untar it, type +Tinc comes in a convenient autoconf/automake package, which you can just +treat the same as any other package. Which is just untar it, type './configure' and then 'make'. More detailed instructions are in the file 'INSTALL', which is included in the source distribution. @@ -436,59 +486,43 @@ File: tinc.info, Node: Building and installing tinc, Next: System files, Up: Detailed instructions on configuring the source, building tinc and installing tinc can be found in the file called 'INSTALL'. - If you happen to have a binary package for tinc for your -distribution, you can use the package management tools of that -distribution to install tinc. The documentation that comes along with -your distribution will tell you how to do that. +If you happen to have a binary package for tinc for your distribution, +you can use the package management tools of that distribution to install +tinc. The documentation that comes along with your distribution will +tell you how to do that. * Menu: -* Darwin (Mac OS X) build environment:: -* Cygwin (Windows) build environment:: +* Darwin (MacOS/X) build environment:: * MinGW (Windows) build environment::  -File: tinc.info, Node: Darwin (Mac OS X) build environment, Next: Cygwin (Windows) build environment, Up: Building and installing tinc +File: tinc.info, Node: Darwin (MacOS/X) build environment, Next: MinGW (Windows) build environment, Up: Building and installing tinc -3.1.1 Darwin (Mac OS X) build environment ------------------------------------------ +3.1.1 Darwin (MacOS/X) build environment +---------------------------------------- In order to build tinc on Darwin, you need to install Xcode from . It might also help to install a recent version of Fink from . - You need to download and install LibreSSL (or OpenSSL) and LZO, -either directly from their websites (see *note Libraries::) or using -Fink. +You need to download and install LibreSSL (or OpenSSL) and LZO, either +directly from their websites (see *note Libraries::) or using Fink.  -File: tinc.info, Node: Cygwin (Windows) build environment, Next: MinGW (Windows) build environment, Prev: Darwin (Mac OS X) build environment, Up: Building and installing tinc +File: tinc.info, Node: MinGW (Windows) build environment, Prev: Darwin (MacOS/X) build environment, Up: Building and installing tinc -3.1.2 Cygwin (Windows) build environment ----------------------------------------- - -If Cygwin hasn't already been installed, install it directly from -. - - When tinc is compiled in a Cygwin environment, it can only be run in -this environment, but all programs, including those started outside the -Cygwin environment, will be able to use the VPN. It will also support -all features. - - -File: tinc.info, Node: MinGW (Windows) build environment, Prev: Cygwin (Windows) build environment, Up: Building and installing tinc - -3.1.3 MinGW (Windows) build environment +3.1.2 MinGW (Windows) build environment --------------------------------------- You will need to install the MinGW environment from . You also need to download and install LibreSSL (or OpenSSL) and LZO. - When tinc is compiled using MinGW it runs natively under Windows, it -is not necessary to keep MinGW installed. +When tinc is compiled using MinGW it runs natively under Windows, it is +not necessary to keep MinGW installed. - When detaching, tinc will install itself as a service, which will be +When detaching, tinc will install itself as a service, which will be restarted automatically after reboots.  @@ -514,8 +548,8 @@ File: tinc.info, Node: Device files, Next: Other files, Up: System files Most operating systems nowadays come with the necessary device files by default, or they have a mechanism to create them on demand. - If you use Linux and do not have udev installed, you may need to -create the following device file if it does not exist: +If you use Linux and do not have udev installed, you may need to create +the following device file if it does not exist: mknod -m 600 /dev/net/tun c 10 200 @@ -537,7 +571,7 @@ symbolic name. For example: ............... You may add this line to '/etc/services'. The effect is that you may -supply a 'tinc' as a valid port number to some programs. The number 655 +supply 'tinc' as a valid port number to some programs. The number 655 is registered with the IANA. tinc 655/tcp TINC @@ -556,7 +590,6 @@ File: tinc.info, Node: Configuration, Next: Running tinc, Prev: Installation, * Multiple networks:: * How connections work:: * Configuration files:: -* Generating keypairs:: * Network interfaces:: * Example configuration:: @@ -576,13 +609,19 @@ Do you want to run tinc in router mode or switch mode? These questions can only be answered by yourself, you will not find the answers in this documentation. Make sure you have an adequate understanding of networks in general. A good resource on networking is the Linux Network -Administrators Guide (http://www.tldp.org/LDP/nag2/). +Administrators Guide (https://www.tldp.org/LDP/nag2/). - If you have everything clearly pictured in your mind, proceed in the -following order: First, generate the configuration files ('tinc.conf', -your host configuration file, 'tinc-up' and perhaps 'tinc-down'). Then -generate the keypairs. Finally, distribute the host configuration -files. These steps are described in the subsections below. +If you have everything clearly pictured in your mind, proceed in the +following order: First, create the initial configuration files and +public/private key pairs using the following command: + tinc -n NETNAME init NAME +Second, use 'tinc -n NETNAME add ...' to further configure tinc. +Finally, export your host configuration file using 'tinc -n NETNAME +export' and send it to those people or computers you want tinc to +connect to. They should send you their host configuration file back, +which you can import using 'tinc -n NETNAME import'. + +These steps are described in the subsections below.  File: tinc.info, Node: Multiple networks, Next: How connections work, Prev: Configuration introduction, Up: Configuration @@ -593,27 +632,25 @@ File: tinc.info, Node: Multiple networks, Next: How connections work, Prev: C In order to allow you to run more than one tinc daemon on one computer, for instance if your computer is part of more than one VPN, you can assign a NETNAME to your VPN. It is not required if you only run one -tinc daemon, it doesn't even have to be the same on all the sites of +tinc daemon, it doesn't even have to be the same on all the nodes of your VPN, but it is recommended that you choose one anyway. - We will assume you use a netname throughout this document. This -means that you call tincd with the -n argument, which will assign a -netname to this daemon. +We will assume you use a netname throughout this document. This means +that you call tinc with the -n argument, which will specify the netname. - The effect of this is that the daemon will set its configuration root -to '/etc/tinc/NETNAME/', where NETNAME is your argument to the -n -option. You'll notice that it appears in syslog as 'tinc.NETNAME'. +The effect of this option is that tinc will set its configuration root +to '/usr/local/etc/tinc/NETNAME/', where NETNAME is your argument to the +-n option. You will also notice that log messages it appears in syslog +as coming from 'tinc.NETNAME', and on Linux, unless specified otherwise, +the name of the virtual network interface will be the same as the +network name. - However, it is not strictly necessary that you call tinc with the -n -option. In this case, the network name would just be empty, and it will -be used as such. tinc now looks for files in '/etc/tinc/', instead of -'/etc/tinc/NETNAME/'; the configuration file should be -'/etc/tinc/tinc.conf', and the host configuration files are now expected -to be in '/etc/tinc/hosts/'. - - But it is highly recommended that you use this feature of tinc, -because it will be so much clearer whom your daemon talks to. Hence, we -will assume that you use it. +However, it is not strictly necessary that you call tinc with the -n +option. If you do not use it, the network name will just be empty, and +tinc will look for files in '/usr/local/etc/tinc/' instead of +'/usr/local/etc/tinc/NETNAME/'; the configuration file will then be +'/usr/local/etc/tinc/tinc.conf', and the host configuration files are +expected to be in '/usr/local/etc/tinc/hosts/'.  File: tinc.info, Node: How connections work, Next: Configuration files, Prev: Multiple networks, Up: Configuration @@ -622,56 +659,77 @@ File: tinc.info, Node: How connections work, Next: Configuration files, Prev: ======================== When tinc starts up, it parses the command-line options and then reads -in the configuration file tinc.conf. If it sees one or more 'ConnectTo' -values pointing to other tinc daemons in that file, it will try to -connect to those other daemons. Whether this succeeds or not and -whether 'ConnectTo' is specified or not, tinc will listen for incoming -connection from other daemons. If you did specify a 'ConnectTo' value -and the other side is not responding, tinc will keep retrying. This -means that once started, tinc will stay running until you tell it to -stop, and failures to connect to other tinc daemons will not stop your -tinc daemon for trying again later. This means you don't have to -intervene if there are temporary network problems. +in the configuration file tinc.conf. It will then start listening for +incoming connection from other daemons, and will by default also +automatically try to connect to known peers. By default, tinc will try +to keep at least 3 working meta-connections alive at all times. - There is no real distinction between a server and a client in tinc. -If you wish, you can view a tinc daemon without a 'ConnectTo' value as a -server, and one which does specify such a value as a client. It does -not matter if two tinc daemons have a 'ConnectTo' value pointing to each -other however. +There is no real distinction between a server and a client in tinc. If +you wish, you can view a tinc daemon without a 'ConnectTo' statement in +tinc.conf and 'AutoConnect = no' as a server, and one which does have +one or more 'ConnectTo' statements or 'Autoconnect = yes' (which is the +default) as a client. It does not matter if two tinc daemons have a +'ConnectTo' value pointing to each other however. + +Connections specified using 'ConnectTo' are so-called meta-connections. +Tinc daemons exchange information about all other daemon they know about +via these meta-connections. After learning about all the daemons in the +VPN, tinc will create other connections as necessary in order to +communicate with them. For example, if there are three daemons named A, +B and C, and A has 'ConnectTo = B' in its tinc.conf file, and C has +'ConnectTo = B' in its tinc.conf file, then A will learn about C from B, +and will be able to exchange VPN packets with C without the need to have +'ConnectTo = C' in its tinc.conf file. + +It could be that some daemons are located behind a Network Address +Translation (NAT) device, or behind a firewall. In the above scenario +with three daemons, if A and C are behind a NAT, B will automatically +help A and C punch holes through their NAT, in a way similar to the STUN +protocol, so that A and C can still communicate with each other +directly. It is not always possible to do this however, and firewalls +might also prevent direct communication. In that case, VPN packets +between A and C will be forwarded by B. + +In effect, all nodes in the VPN will be able to talk to each other, as +long as there is a path of meta-connections between them, and whenever +possible, two nodes will communicate with each other directly.  -File: tinc.info, Node: Configuration files, Next: Generating keypairs, Prev: How connections work, Up: Configuration +File: tinc.info, Node: Configuration files, Next: Network interfaces, Prev: How connections work, Up: Configuration 4.4 Configuration files ======================= The actual configuration of the daemon is done in the file -'/etc/tinc/NETNAME/tinc.conf' and at least one other file in the -directory '/etc/tinc/NETNAME/hosts/'. +'/usr/local/etc/tinc/NETNAME/tinc.conf' and at least one other file in +the directory '/usr/local/etc/tinc/NETNAME/hosts/'. - An optional directory '/etc/tinc/NETNAME/conf.d' can be added from -which any .conf file will be read. +An optional directory '/usr/local/etc/tinc/NETNAME/conf.d' can be added +from which any .conf file will be read. - These file consists of comments (lines started with a #) or -assignments in the form of +These file consists of comments (lines started with a #) or assignments +in the form of Variable = Value. - The variable names are case insensitive, and any spaces, tabs, -newlines and carriage returns are ignored. Note: it is not required -that you put in the '=' sign, but doing so improves readability. If you -leave it out, remember to replace it with at least one space character. +The variable names are case insensitive, and any spaces, tabs, newlines +and carriage returns are ignored. Note: it is not required that you put +in the '=' sign, but doing so improves readability. If you leave it +out, remember to replace it with at least one space character. - The server configuration is complemented with host specific +The server configuration is complemented with host specific configuration (see the next section). Although all host configuration options for the local node listed in this document can also be put in -'/etc/tinc/NETNAME/tinc.conf', it is recommended to put host specific -configuration options in the host configuration file, as this makes it -easy to exchange with other nodes. +'/usr/local/etc/tinc/NETNAME/tinc.conf', it is recommended to put host +specific configuration options in the host configuration file, as this +makes it easy to exchange with other nodes. - In this section all valid variables are listed in alphabetical order. -The default value is given between parentheses, other comments are -between square brackets. +You can edit the config file manually, but it is recommended that you +use the tinc command to change configuration variables for you. + +In the following two subsections all valid variables are listed in +alphabetical order. The default value is given between parentheses, +other comments are between square brackets. * Menu: @@ -692,18 +750,16 @@ AddressFamily = (any) system both IPv4 and IPv6 or just IPv6 listening sockets will be created. -BindToAddress =
[] [experimental] - If your computer has more than one IPv4 or IPv6 address, tinc will - by default listen on all of them for incoming connections. - Multiple BindToAddress variables may be specified, in which case - listening sockets for each specified address are made. +AutoConnect = (yes) + If set to yes, tinc will automatically set up meta connections to + other nodes, without requiring CONNECTTO variables. - If no PORT is specified, the socket will be bound to the port - specified by the Port option, or to port 655 if neither is given. - To only bind to a specific port but not to a specific address, use - "*" for the ADDRESS. - - This option may not work on all platforms. +BindToAddress =
[] + This is the same as ListenAddress, however the address given with + the BindToAddress option will also be used for outgoing + connections. This is useful if your computer has more than one + IPv4 or IPv6 address, and you want tinc to only use a specific one + for outgoing packets. BindToInterface = [experimental] If you have more than one network interface in your computer, tinc @@ -711,7 +767,9 @@ BindToInterface = [experimental] is possible to bind tinc to a single interface like eth0 or ppp0 with this variable. - This option may not work on all platforms. + This option may not work on all platforms. Also, on some platforms + it will not actually bind to an interface, but rather to the + address that the interface has at the moment a socket is created. Broadcast = (mst) [experimental] This option selects the way broadcast packets are sent to other @@ -733,6 +791,18 @@ Broadcast = (mst) [experimental] broadcast packets will only be sent to nodes which we have a meta connection to. +BroadcastSubnet = ADDRESS[/PREFIXLENGTH] + Declares a broadcast subnet. Any packet with a destination address + falling into such a subnet will be routed as a broadcast (provided + all nodes have it declared). This is most useful to declare subnet + broadcast addresses (e.g. 10.42.255.255), otherwise tinc won't + know what to do with them. + + Note that global broadcast addresses (MAC ff:ff:ff:ff:ff:ff, IPv4 + 255.255.255.255), as well as multicast space (IPv4 224.0.0.0/4, + IPv6 ff00::/8) are always considered broadcast addresses and don't + need to be declared. + ConnectTo = Specifies which other tinc daemon to connect to on startup. Multiple ConnectTo variables may be specified, in which case @@ -740,9 +810,9 @@ ConnectTo = names should be known to this tinc daemon (i.e., there should be a host configuration file for the name on the ConnectTo line). - If you don't specify a host with ConnectTo, tinc won't try to - connect to other daemons at all, and will instead just listen for - incoming connections. + If you don't specify a host with ConnectTo and have disabled + AutoConnect, tinc won't try to connect to other daemons at all, and + will instead just listen for incoming connections. DecrementTTL = (no) [experimental] When enabled, tinc will decrement the Time To Live field in IPv4 @@ -755,9 +825,17 @@ DecrementTTL = (no) [experimental] Device = ('/dev/tap0', '/dev/net/tun' or other depending on platform) The virtual network device to use. Tinc will automatically detect - what kind of device it is. Under Windows, use INTERFACE instead of - DEVICE. Note that you can only use one device per daemon. See - also *note Device files::. + what kind of device it is. Note that you can only use one device + per daemon. Under Windows, use INTERFACE instead of DEVICE. Note + that you can only use one device per daemon. See also *note Device + files::. + +DeviceStandby = (no) + When disabled, tinc calls 'tinc-up' on startup, and 'tinc-down' on + shutdown. When enabled, tinc will only call 'tinc-up' when at + least one node is reachable, and will call 'tinc-down' as soon as + no nodes are reachable. On Windows, this also determines when the + virtual network interface "cable" is "plugged". DeviceType = (platform dependent) The type of the virtual network device. Tinc will normally @@ -789,15 +867,23 @@ DeviceType = (platform dependent) that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. + fd + Use a file descriptor, given directly as an integer or passed + through a unix domain socket. On Linux, an abstract socket + address can be specified by using '@' as a prefix. All + packets are read from this interface. Packets received for + the local node are written to it. + uml (not compiled in by default) Create a UNIX socket with the filename specified by DEVICE, or - '/run/NETNAME.umlsocket' if not specified. Tinc will wait for - a User Mode Linux instance to connect to this socket. + '/usr/local/var/run/NETNAME.umlsocket' if not specified. Tinc + will wait for a User Mode Linux instance to connect to this + socket. vde (not compiled in by default) Uses the libvdeplug library to connect to a Virtual Distributed Ethernet switch, using the UNIX socket specified - by DEVICE, or '/run/vde.ctl' if not specified. + by DEVICE, or '/usr/local/var/run/vde.ctl' if not specified. Also, in case tinc does not seem to correctly interpret packets received from the virtual network device, it can be used to change @@ -836,6 +922,17 @@ DirectOnly = (no) [experimental] IndirectData option, packets for nodes for which we do not have a meta connection with are also dropped. +Ed25519PrivateKeyFile = ('/usr/local/etc/tinc/NETNAME/ed25519_key.priv') + The file in which the private Ed25519 key of this tinc daemon + resides. This is only used if ExperimentalProtocol is enabled. + +ExperimentalProtocol = (yes) + When this option is enabled, the SPTPS protocol will be used when + connecting to nodes that also support it. Ephemeral ECDH will be + used for key exchanges, and Ed25519 will be used instead of RSA for + authentication. When enabled, an Ed25519 key must have been + generated before with 'tinc generate-ed25519-keys'. + Forwarding = (internal) [experimental] This option selects the way indirect packets are forwarded. @@ -851,18 +948,18 @@ Forwarding = (internal) [experimental] another forwarding mode, don't change it. kernel - Incoming packets are always sent to the TUN/TAP device, even - if the packets are not for the local node. This is less - efficient, but allows the kernel to apply its routing and - firewall rules on them, and can also help debugging. + Incoming packets using the legacy protocol are always sent to + the TUN/TAP device, even if the packets are not for the local + node. This is less efficient, but allows the kernel to apply + its routing and firewall rules on them, and can also help + debugging. Incoming packets using the SPTPS protocol are + dropped, since they are end-to-end encrypted. -GraphDumpFile = [experimental] - If this option is present, tinc will dump the current network graph - to the file FILENAME every minute, unless there were no changes to - the graph. The file is in a format that can be read by graphviz - tools. If FILENAME starts with a pipe symbol |, then the rest of - the filename is interpreted as a shell command that is executed, - the graph is then sent to stdin. +FWMark = (0) [experimental] + When set to a non-zero value, all TCP and UDP sockets created by + tinc will use the given value as the firewall mark. This can be + used for mark-based routing or for packet filtering. This option + is currently only supported on Linux. Hostnames = (no) This option selects whether IP addresses (both real and on the VPN) @@ -875,9 +972,6 @@ Hostnames = (no) configuration file, but whether hostnames should be resolved while logging. -IffOneQueue = (no) [experimental] - (Linux only) Set IFF_ONE_QUEUE flag on TUN/TAP devices. - Interface = Defines the name of the interface corresponding to the virtual network device. Depending on the operating system and the type of @@ -886,38 +980,39 @@ Interface = interface will be used. If you specified a Device, this variable is almost always already correctly set. -KeyExpire = (3600) - This option controls the time the encryption keys used to encrypt - the data are valid. It is common practice to change keys at - regular intervals to make it even harder for crackers, even though - it is thought to be nearly impossible to crack a single key. +ListenAddress =
[] + If your computer has more than one IPv4 or IPv6 address, tinc will + by default listen on all of them for incoming connections. This + option can be used to restrict which addresses tinc listens on. + Multiple ListenAddress variables may be specified, in which case + listening sockets for each specified address are made. -LocalDiscovery = (no) [experimental] + If no PORT is specified, the socket will listen on the port + specified by the Port option, or to port 655 if neither is given. + To only listen on a specific port but not to a specific address, + use '*' for the ADDRESS. + +LocalDiscovery = (no) When enabled, tinc will try to detect peers that are on the same local network. This will allow direct communication using LAN addresses, even if both peers are behind a NAT and they only ConnectTo a third node outside the NAT, which normally would prevent the peers from learning each other's LAN address. - Currently, local discovery is implemented by sending broadcast - packets to the LAN during path MTU discovery. This feature may not - work in all possible situations. + Currently, local discovery is implemented by sending some packets + to the local address of the node during UDP discovery. This will + not work with old nodes that don't transmit their local address. -MACExpire = (600) - This option controls the amount of time MAC addresses are kept - before they are removed. This only has effect when Mode is set to - "switch". - -MaxTimeout = (900) - This is the maximum delay before trying to reconnect to other tinc - daemons. +LogLevel = (0) + This option controls the verbosity of the logging. See *note Debug + levels::. Mode = (router) This option selects the way packets are routed to other daemons. router In this mode Subnet variables in the host configuration files - will be used to form a routing table. Only unicast packets of + will be used to form a routing table. Only packets of routable protocols (IPv4 and IPv6) are supported in this mode. This is the default mode, and unless you really know you need @@ -939,10 +1034,30 @@ Mode = (router) every packet will be broadcast to the other daemons while no routing table is managed. +InvitationExpire = (604800) + This option controls the time invitations are valid. + +KeyExpire = (3600) + This option controls the time the encryption keys used to encrypt + the data are valid. It is common practice to change keys at + regular intervals to make it even harder for crackers, even though + it is thought to be nearly impossible to crack a single key. + +MACExpire = (600) + This option controls the amount of time MAC addresses are kept + before they are removed. This only has effect when Mode is set to + 'switch'. + +MaxConnectionBurst = (100) + This option controls how many connections tinc accepts in quick + succession. If there are more connections than the given number in + a short time interval, tinc will reduce the number of accepted + connections to only one per second, until the burst has passed. + Name = [required] This is a symbolic name for this connection. The name must consist - only of alphanumeric and underscore characters (a-z, A-Z, 0-9 and - _). + only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and + _), and is case sensitive. If Name starts with a $, then the contents of the environment variable that follows will be used. In that case, invalid @@ -971,10 +1086,10 @@ PrivateKey = [obsolete] This prevents accidental eavesdropping if you are editing the configuration file. -PrivateKeyFile = ('/etc/tinc/NETNAME/rsa_key.priv') +PrivateKeyFile = ('/usr/local/etc/tinc/NETNAME/rsa_key.priv') This is the full path name of the RSA private key file that was - generated by 'tincd --generate-keys'. It must be a full path, not - a relative directory. + generated by 'tinc generate-keys'. It must be a full path, not a + relative directory. ProcessPriority = When this option is used the priority of the tincd process will be @@ -1004,10 +1119,10 @@ Proxy = socks4 | socks5 | http | exec ... [experimental] connection. The environment variables 'NAME', 'NODE', 'REMOTEADDRES' and 'REMOTEPORT' are available. -ReplayWindow = (16) +ReplayWindow = (32) This is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet per - bit, so for example the default setting of 16 will track up to 128 + bit, so for example the default setting of 32 will track up to 256 packets in the window. In high bandwidth scenarios, setting this to a higher value can reduce packet loss from the interaction of replay tracking with underlying real packet loss and/or reordering. @@ -1018,26 +1133,74 @@ ReplayWindow = (16) StrictSubnets = (no) [experimental] When this option is enabled tinc will only use Subnet statements which are present in the host config files in the local - '/etc/tinc/NETNAME/hosts/' directory. Subnets learned via - connections to other nodes and which are not present in the local - host config files are ignored. + '/usr/local/etc/tinc/NETNAME/hosts/' directory. Subnets learned + via connections to other nodes and which are not present in the + local host config files are ignored. TunnelServer = (no) [experimental] When this option is enabled tinc will no longer forward information between other tinc daemons, and will only allow connections with nodes for which host config files are present in the local - '/etc/tinc/NETNAME/hosts/' directory. Setting this options also - implicitly sets StrictSubnets. + '/usr/local/etc/tinc/NETNAME/hosts/' directory. Setting this + options also implicitly sets StrictSubnets. -UDPRcvBuf = (OS default) +UDPDiscovery = (yes) + When this option is enabled tinc will try to establish UDP + connectivity to nodes, using TCP while it determines if a node is + reachable over UDP. If it is disabled, tinc always assumes a node + is reachable over UDP. Note that tinc will never use UDP with nodes + that have TCPOnly enabled. + +UDPDiscoveryKeepaliveInterval = (9) + The minimum amount of time between sending UDP ping datagrams to + check UDP connectivity once it has been established. Note that + these pings are large, since they are used to verify link MTU as + well. + +UDPDiscoveryInterval = (2) + The minimum amount of time between sending UDP ping datagrams to + try to establish UDP connectivity. + +UDPDiscoveryTimeout = (30) + If tinc doesn't receive any UDP ping replies over the specified + interval, it will assume UDP communication is broken and will fall + back to TCP. + +UDPInfoInterval = (5) + The minimum amount of time between sending periodic updates about + UDP addresses, which are mostly useful for UDP hole punching. + +UDPRcvBuf = (1048576) Sets the socket receive buffer size for the UDP socket, in bytes. - If unset, the default buffer size will be used by the operating - system. + If set to zero, the default buffer size will be used by the + operating system. Note: this setting can have a significant impact + on performance, especially raw throughput. -UDPSndBuf = Pq OS default +UDPSndBuf = (1048576) Sets the socket send buffer size for the UDP socket, in bytes. If - unset, the default buffer size will be used by the operating - system. + set to zero, the default buffer size will be used by the operating + system. Note: this setting can have a significant impact on + performance, especially raw throughput. + +UPnP = (no) + If this option is enabled then tinc will search for UPnP-IGD + devices on the local network. It will then create and maintain + port mappings for tinc's listening TCP and UDP ports. If set to + 'udponly', tinc will only create a mapping for its UDP (data) port, + not for its TCP (metaconnection) port. Note that tinc must have + been built with miniupnpc support for this feature to be available. + Furthermore, be advised that enabling this can have security + implications, because the miniupnpc library that tinc uses might + not be well-hardened with regard to malicious UPnP replies. + +UPnPDiscoverWait = (5) + The amount of time to wait for replies when probing the local + network for UPnP devices. + +UPnPRefreshPeriod = (5) + How often tinc will re-add the port mapping, in case it gets reset + on the UPnP device. This also controls the duration of the port + mapping itself, which will be set to twice that duration.  File: tinc.info, Node: Host configuration variables, Next: Scripts, Prev: Main configuration variables, Up: Configuration files @@ -1053,11 +1216,13 @@ Address = [] [recommended] can be specified, in which case each address will be tried until a working connection has been established. -Cipher = (aes-256-cbc) - The symmetric cipher algorithm used to encrypt UDP packets. Any - cipher supported by LibreSSL or OpenSSL is recognized. - Furthermore, specifying "none" will turn off packet encryption. It - is best to use only those ciphers which support CBC mode. +Cipher = (blowfish) + The symmetric cipher algorithm used to encrypt UDP packets using + the legacy protocol. Any cipher supported by LibreSSL or OpenSSL + is recognized. Furthermore, specifying 'none' will turn off packet + encryption. It is best to use only those ciphers which support CBC + mode. This option has no effect for connections using the SPTPS + protocol, which always use AES-256-CTR. ClampMSS = (yes) This option specifies whether tinc should clamp the maximum segment @@ -1068,25 +1233,26 @@ ClampMSS = (yes) Compression = (0) This option sets the level of compression used for UDP packets. Possible values are 0 (off), 1 (fast zlib) and any integer up to 9 - (best zlib), 10 (fast lzo) and 11 (best lzo). + (best zlib), 10 (fast LZO) and 11 (best LZO). -Digest = (sha256) - The digest algorithm used to authenticate UDP packets. Any digest - supported by LibreSSL or OpenSSL is recognized. Furthermore, - specifying "none" will turn off packet authentication. +Digest = (sha1) + The digest algorithm used to authenticate UDP packets using the + legacy protocol. Any digest supported by LibreSSL or OpenSSL is + recognized. Furthermore, specifying 'none' will turn off packet + authentication. This option has no effect for connections using + the SPTPS protocol, which always use HMAC-SHA-256. IndirectData = (no) - This option specifies whether other tinc daemons besides the one - you specified with ConnectTo can make a direct connection to you. - This is especially useful if you are behind a firewall and it is - impossible to make a connection from the outside to your tinc - daemon. Otherwise, it is best to leave this option out or set it - to no. + When set to yes, other nodes which do not already have a meta + connection to you will not try to establish direct communication + with you. It is best to leave this option out or set it to no. MACLength = (4) The length of the message authentication code used to authenticate - UDP packets. Can be anything from 0 up to the length of the digest - produced by the digest algorithm. + UDP packets using the legacy protocol. Can be anything from 0 up + to the length of the digest produced by the digest algorithm. This + option has no effect for connections using the SPTPS protocol, + which never truncate MACs. PMTU = (1514) This option controls the initial path MTU to this node. @@ -1096,6 +1262,11 @@ PMTUDiscovery = (yes) to this node. After the path MTU has been discovered, it will be enforced on the VPN. +MTUInfoInterval = (5) + The minimum amount of time between sending periodic updates about + relay path MTU. Useful for quickly determining MTU to indirect + nodes. + Port = (655) This is the port this tinc daemon listens on. You can use decimal portnumbers or symbolic names (as listed in '/etc/services'). @@ -1105,8 +1276,8 @@ PublicKey = [obsolete] PublicKeyFile = [obsolete] This is the full path name of the RSA public key file that was - generated by 'tincd --generate-keys'. It must be a full path, not - a relative directory. + generated by 'tinc generate-keys'. It must be a full path, not a + relative directory. From version 1.0pre4 on tinc will store the public key directly into the host configuration file in PEM format, the above two @@ -1144,15 +1315,18 @@ Subnet = reachable, in which case the node with the next highest priority will be tried, and so on. -TCPonly = (no) [deprecated] +TCPonly = (no) If this variable is set to yes, then the packets are tunnelled over a TCP connection instead of a UDP connection. This is especially useful for those who want to run a tinc daemon from behind a masquerading firewall, or if UDP packet routing is disabled somehow. Setting this options also implicitly sets IndirectData. - Since version 1.0.10, tinc will automatically detect whether - communication via UDP is possible or not. +Weight = + If this variable is set, it overrides the weight given to + connections made with another host. A higher weight means a lower + priority is given to this connection when broadcasting or + forwarding packets.  File: tinc.info, Node: Scripts, Next: How to configure, Prev: Host configuration variables, Up: Configuration files @@ -1165,15 +1339,15 @@ also run scripts at certain moments. Below is a list of filenames of scripts and a description of when they are run. A script is only run if it exists and if it is executable. - Scripts are run synchronously; this means that tinc will temporarily +Scripts are run synchronously; this means that tinc will temporarily stop processing packets until the called script finishes executing. This guarantees that scripts will execute in the exact same order as the events that trigger them. If you need to run commands asynchronously, you have to ensure yourself that they are being run in the background. - Under Windows (not Cygwin), the scripts must have the extension .bat. +Under Windows, the scripts should have the extension '.bat' or '.cmd'. -'/etc/tinc/NETNAME/tinc-up' +'/usr/local/etc/tinc/NETNAME/tinc-up' This is the most important script. If it is present it will be executed right after the tinc daemon has been started and has connected to the virtual network device. It should be used to set @@ -1183,34 +1357,41 @@ you have to ensure yourself that they are being run in the background. Under Windows you can use the Network Connections control panel instead of creating this script. -'/etc/tinc/NETNAME/tinc-down' +'/usr/local/etc/tinc/NETNAME/tinc-down' This script is started right before the tinc daemon quits. -'/etc/tinc/NETNAME/hosts/HOST-up' +'/usr/local/etc/tinc/NETNAME/hosts/HOST-up' This script is started when the tinc daemon with name HOST becomes reachable. -'/etc/tinc/NETNAME/hosts/HOST-down' +'/usr/local/etc/tinc/NETNAME/hosts/HOST-down' This script is started when the tinc daemon with name HOST becomes unreachable. -'/etc/tinc/NETNAME/host-up' +'/usr/local/etc/tinc/NETNAME/host-up' This script is started when any host becomes reachable. -'/etc/tinc/NETNAME/host-down' +'/usr/local/etc/tinc/NETNAME/host-down' This script is started when any host becomes unreachable. -'/etc/tinc/NETNAME/subnet-up' - This script is started when a subnet becomes reachable. The Subnet +'/usr/local/etc/tinc/NETNAME/subnet-up' + This script is started when a Subnet becomes reachable. The Subnet and the node it belongs to are passed in environment variables. -'/etc/tinc/NETNAME/subnet-down' - This script is started when a subnet becomes unreachable. +'/usr/local/etc/tinc/NETNAME/subnet-down' + This script is started when a Subnet becomes unreachable. - The scripts are started without command line arguments, but can make -use of certain environment variables. Under UNIX like operating systems -the names of environment variables must be preceded by a $ in scripts. -Under Windows, in '.bat' files, they have to be put between % signs. +'/usr/local/etc/tinc/NETNAME/invitation-created' + This script is started when a new invitation has been created. + +'/usr/local/etc/tinc/NETNAME/invitation-accepted' + This script is started when an invitation has been used. + +The scripts are started without command line arguments, but can make use +of certain environment variables. Under UNIX like operating systems the +names of environment variables must be preceded by a $ in scripts. +Under Windows, in '.bat' or '.cmd' files, they have to be put between % +signs. 'NETNAME' If a netname was specified, this environment variable contains it. @@ -1244,95 +1425,163 @@ Under Windows, in '.bat' files, they have to be put between % signs. When a subnet becomes (un)reachable, this is set to the subnet weight. +'INVITATION_FILE' + When the 'invitation-created' script is called, this is set to the + file where the invitation details will be stored. + +'INVITATION_URL' + When the 'invitation-created' script is called, this is set to the + invitation URL that has been created. + +Do not forget that under UNIX operating systems, you have to make the +scripts executable, using the command 'chmod a+x script'. +  File: tinc.info, Node: How to configure, Prev: Scripts, Up: Configuration files 4.4.4 How to configure ---------------------- -Step 1. Creating the main configuration file -............................................ - -The main configuration file will be called -'/etc/tinc/NETNAME/tinc.conf'. Adapt the following example to create a -basic configuration file: - - Name = YOURNAME - Device = /dev/tap0 - - Then, if you know to which other tinc daemon(s) yours is going to -connect, add 'ConnectTo' values. - -Step 2. Creating your host configuration file +Step 1. Creating initial configuration files. ............................................. -If you added a line containing 'Name = yourname' in the main -configuration file, you will need to create a host configuration file -'/etc/tinc/NETNAME/hosts/yourname'. Adapt the following example to -create a host configuration file: +The initial directory structure, configuration files and public/private +key pairs are created using the following command: - Address = your.real.hostname.org - Subnet = 192.168.1.0/24 + tinc -n NETNAME init NAME - You can also use an IP address instead of a hostname. The 'Subnet' -specifies the address range that is local for _your part of the VPN -only_. If you have multiple address ranges you can specify more than -one 'Subnet'. You might also need to add a 'Port' if you want your tinc -daemon to run on a different port number than the default (655). +(You will need to run this as root, or use 'sudo'.) This will create +the configuration directory '/usr/local/etc/tinc/NETNAME.', and inside +it will create another directory named 'hosts/'. In the configuration +directory, it will create the file 'tinc.conf' with the following +contents: + + Name = NAME + +It will also create private RSA and Ed25519 keys, which will be stored +in the files 'rsa_key.priv' and 'ed25519_key.priv'. It will also create +a host configuration file 'hosts/NAME', which will contain the +corresponding public RSA and Ed25519 keys. + +Finally, on UNIX operating systems, it will create an executable script +'tinc-up', which will initially not do anything except warning that you +should edit it. + +Step 2. Modifying the initial configuration. +............................................ + +Unless you want to use tinc in switch mode, you should now configure +which range of addresses you will use on the VPN. Let's assume you will +be part of a VPN which uses the address range 192.168.0.0/16, and you +yourself have a smaller portion of that range: 192.168.2.0/24. Then you +should run the following command: + + tinc -n NETNAME add subnet 192.168.2.0/24 + +This will add a Subnet statement to your host configuration file. Try +opening the file '/usr/local/etc/tinc/NETNAME/hosts/NAME' in an editor. +You should now see a file containing the public RSA and Ed25519 keys +(which looks like a bunch of random characters), and the following line +at the bottom: + + Subnet = 192.168.2.0/24 + +If you will use more than one address range, you can add more Subnets. +For example, if you also use the IPv6 subnet fec0:0:0:2::/64, you can +add it as well: + + tinc -n NETNAME add subnet fec0:0:0:2::/24 + +This will add another line to the file 'hosts/NAME'. If you make a +mistake, you can undo it by simply using 'del' instead of 'add'. + +If you want other tinc daemons to create meta-connections to your +daemon, you should add your public IP address or hostname to your host +configuration file. For example, if your hostname is foo.example.org, +run: + + tinc -n NETNAME add address foo.example.org + +Step 2. Exchanging configuration files. +....................................... + +In order for two tinc daemons to be able to connect to each other, they +each need the other's host configuration files. So if you want foo to +be able to connect with bar, You should send 'hosts/NAME' to bar, and +bar should send you his file which you should move to 'hosts/bar'. If +you are on a UNIX platform, you can easily send an email containing the +necessary information using the following command (assuming the owner of +bar has the email address bar@example.org): + + tinc -n NETNAME export | mail -s "My config file" bar@example.org + +If the owner of bar does the same to send his host configuration file to +you, you can probably pipe his email through the following command, or +you can just start this command in a terminal and copy&paste the email: + + tinc -n NETNAME import + +If you are the owner of bar yourself, and you have SSH access to that +computer, you can also swap the host configuration files using the +following command: + + tinc -n NETNAME export \ + | ssh bar.example.org tinc -n NETNAME exchange \ + | tinc -n NETNAME import + +You can repeat this for a few other nodes as well. It is not necessary +to manually exchange host config files between all nodes; after the +initial connections are made tinc will learn about all the other nodes +in the VPN, and will automatically make other connections as necessary.  -File: tinc.info, Node: Generating keypairs, Next: Network interfaces, Prev: Configuration files, Up: Configuration +File: tinc.info, Node: Network interfaces, Next: Example configuration, Prev: Configuration files, Up: Configuration -4.5 Generating keypairs -======================= - -Now that you have already created the main configuration file and your -host configuration file, you can easily create a public/private keypair -by entering the following command: - - tincd -n NETNAME -K - - Tinc will generate a public and a private key and ask you where to -put them. Just press enter to accept the defaults. - - -File: tinc.info, Node: Network interfaces, Next: Example configuration, Prev: Generating keypairs, Up: Configuration - -4.6 Network interfaces +4.5 Network interfaces ====================== Before tinc can start transmitting data over the tunnel, it must set up the virtual network interface. - First, decide which IP addresses you want to have associated with -these devices, and what network mask they must have. +First, decide which IP addresses you want to have associated with these +devices, and what network mask they must have. - Tinc will open a virtual network device ('/dev/tun', '/dev/tap0' or +Tinc will open a virtual network device ('/dev/tun', '/dev/tap0' or similar), which will also create a network interface called something like 'tun0', 'tap0'. If you are using the Linux tun/tap driver, the network interface will by default have the same name as the NETNAME. Under Windows you can change the name of the network interface from the Network Connections control panel. - You can configure the network interface by putting ordinary ifconfig, -route, and other commands to a script named '/etc/tinc/NETNAME/tinc-up'. -When tinc starts, this script will be executed. When tinc exits, it -will execute the script named '/etc/tinc/NETNAME/tinc-down', but -normally you don't need to create that script. +You can configure the network interface by putting ordinary ifconfig, +route, and other commands to a script named +'/usr/local/etc/tinc/NETNAME/tinc-up'. When tinc starts, this script +will be executed. When tinc exits, it will execute the script named +'/usr/local/etc/tinc/NETNAME/tinc-down', but normally you don't need to +create that script. You can manually open the script in an editor, or +use the following command: - An example 'tinc-up' script: + tinc -n NETNAME edit tinc-up + +An example 'tinc-up' script, that would be appropriate for the scenario +in the previous section, is: #!/bin/sh - ifconfig $INTERFACE 192.168.1.1 netmask 255.255.0.0 + ifconfig $INTERFACE 192.168.2.1 netmask 255.255.0.0 + ip addr add fec0:0:0:2::/48 dev $INTERFACE - This script gives the interface an IP address and a netmask. The -kernel will also automatically add a route to this interface, so -normally you don't need to add route commands to the 'tinc-up' script. -The kernel will also bring the interface up after this command. The -netmask is the mask of the _entire_ VPN network, not just your own -subnet. +The first command gives the interface an IPv4 address and a netmask. +The kernel will also automatically add an IPv4 route to this interface, +so normally you don't need to add route commands to the 'tinc-up' +script. The kernel will also bring the interface up after this command. +The netmask is the mask of the _entire_ VPN network, not just your own +subnet. The second command gives the interface an IPv6 address and +netmask, which will also automatically add an IPv6 route. If you only +want to use 'ip addr' commands on Linux, don't forget that it doesn't +bring the interface up, unlike ifconfig, so you need to add 'ip link set +$INTERFACE up' in that case. - The exact syntax of the ifconfig and route commands differs from +The exact syntax of the ifconfig and route commands differs from platform to platform. You can look up the commands for setting addresses and adding routes in *note Platform specific information::, but it is best to consult the manpages of those utilities on your @@ -1341,52 +1590,56 @@ platform.  File: tinc.info, Node: Example configuration, Prev: Network interfaces, Up: Configuration -4.7 Example configuration +4.6 Example configuration ========================= Imagine the following situation. Branch A of our example 'company' wants to connect three branch offices in B, C and D using the Internet. All four offices have a 24/7 connection to the Internet. - A is going to serve as the center of the network. B and C will -connect to A, and D will connect to C. Each office will be assigned -their own IP network, 10.x.0.0. +A is going to serve as the center of the network. B and C will connect +to A, and D will connect to C. Each office will be assigned their own IP +network, 10.x.0.0. A: net 10.1.0.0 mask 255.255.0.0 gateway 10.1.54.1 internet IP 1.2.3.4 B: net 10.2.0.0 mask 255.255.0.0 gateway 10.2.1.12 internet IP 2.3.4.5 C: net 10.3.0.0 mask 255.255.0.0 gateway 10.3.69.254 internet IP 3.4.5.6 D: net 10.4.0.0 mask 255.255.0.0 gateway 10.4.3.32 internet IP 4.5.6.7 - Here, "gateway" is the VPN IP address of the machine that is running -the tincd, and "internet IP" is the IP address of the firewall, which -does not need to run tincd, but it must do a port forwarding of TCP and -UDP on port 655 (unless otherwise configured). +Here, "gateway" is the VPN IP address of the machine that is running the +tincd, and "internet IP" is the IP address of the firewall, which does +not need to run tincd, but it must do a port forwarding of TCP and UDP +on port 655 (unless otherwise configured). - In this example, it is assumed that eth0 is the interface that points -to the inner (physical) LAN of the office, although this could also be -the same as the interface that leads to the Internet. The configuration -of the real interface is also shown as a comment, to give you an idea of +In this example, it is assumed that eth0 is the interface that points to +the inner (physical) LAN of the office, although this could also be the +same as the interface that leads to the Internet. The configuration of +the real interface is also shown as a comment, to give you an idea of how these example host is set up. All branches use the netname 'company' for this particular VPN. +Each branch is set up using the 'tinc init' and 'tinc config' commands, +here we just show the end results: + For Branch A ............ _BranchA_ would be configured like this: - In '/etc/tinc/company/tinc-up': +In '/usr/local/etc/tinc/company/tinc-up': + + #!/bin/sh # Real interface of internal network: # ifconfig eth0 10.1.54.1 netmask 255.255.0.0 ifconfig $INTERFACE 10.1.54.1 netmask 255.0.0.0 - and in '/etc/tinc/company/tinc.conf': +and in '/usr/local/etc/tinc/company/tinc.conf': Name = BranchA - Device = /dev/tap0 - On all hosts, '/etc/tinc/company/hosts/BranchA' contains: +On all hosts, '/usr/local/etc/tinc/company/hosts/BranchA' contains: Subnet = 10.1.0.0/16 Address = 1.2.3.4 @@ -1395,32 +1648,32 @@ _BranchA_ would be configured like this: ... -----END RSA PUBLIC KEY----- - Note that the IP addresses of eth0 and tap0 are the same. This is -quite possible, if you make sure that the netmasks of the interfaces are -different. It is in fact recommended to give both real internal network -interfaces and tap interfaces the same IP address, since that will make -things a lot easier to remember and set up. +Note that the IP addresses of eth0 and the VPN interface are the same. +This is quite possible, if you make sure that the netmasks of the +interfaces are different. It is in fact recommended to give both real +internal network interfaces and VPN interfaces the same IP address, +since that will make things a lot easier to remember and set up. For Branch B ............ -In '/etc/tinc/company/tinc-up': +In '/usr/local/etc/tinc/company/tinc-up': + + #!/bin/sh # Real interface of internal network: # ifconfig eth0 10.2.43.8 netmask 255.255.0.0 ifconfig $INTERFACE 10.2.1.12 netmask 255.0.0.0 - and in '/etc/tinc/company/tinc.conf': +and in '/usr/local/etc/tinc/company/tinc.conf': Name = BranchB - ConnectTo = BranchA - Note here that the internal address (on eth0) doesn't have to be the -same as on the tap0 device. Also, ConnectTo is given so that this node -will always try to connect to BranchA. +Note here that the internal address (on eth0) doesn't have to be the +same as on the VPN interface. - On all hosts, in '/etc/tinc/company/hosts/BranchB': +On all hosts, in '/usr/local/etc/tinc/company/hosts/BranchB': Subnet = 10.2.0.0/16 Address = 2.3.4.5 @@ -1432,24 +1685,24 @@ will always try to connect to BranchA. For Branch C ............ -In '/etc/tinc/company/tinc-up': +In '/usr/local/etc/tinc/company/tinc-up': + + #!/bin/sh # Real interface of internal network: # ifconfig eth0 10.3.69.254 netmask 255.255.0.0 ifconfig $INTERFACE 10.3.69.254 netmask 255.0.0.0 - and in '/etc/tinc/company/tinc.conf': +and in '/usr/local/etc/tinc/company/tinc.conf': Name = BranchC - ConnectTo = BranchA - Device = /dev/tap1 - C already has another daemon that runs on port 655, so they have to +C already has another daemon that runs on port 655, so they have to reserve another port for tinc. It knows the portnumber it has to listen on from it's own host configuration file. - On all hosts, in '/etc/tinc/company/hosts/BranchC': +On all hosts, in '/usr/local/etc/tinc/company/hosts/BranchC': Address = 3.4.5.6 Subnet = 10.3.0.0/16 @@ -1462,26 +1715,23 @@ on from it's own host configuration file. For Branch D ............ -In '/etc/tinc/company/tinc-up': +In '/usr/local/etc/tinc/company/tinc-up': + + #!/bin/sh # Real interface of internal network: # ifconfig eth0 10.4.3.32 netmask 255.255.0.0 ifconfig $INTERFACE 10.4.3.32 netmask 255.0.0.0 - and in '/etc/tinc/company/tinc.conf': +and in '/usr/local/etc/tinc/company/tinc.conf': Name = BranchD - ConnectTo = BranchC - Device = /dev/net/tun - D will be connecting to C, which has a tincd running for this network -on port 2000. It knows the port number from the host configuration -file. Also note that since D uses the tun/tap driver, the network -interface will not be called 'tun' or 'tap0' or something like that, but -will have the same name as netname. +D will be connecting to C, which has a tincd running for this network on +port 2000. It knows the port number from the host configuration file. - On all hosts, in '/etc/tinc/company/hosts/BranchD': +On all hosts, in '/usr/local/etc/tinc/company/hosts/BranchD': Subnet = 10.4.0.0/16 Address = 4.5.6.7 @@ -1493,16 +1743,13 @@ will have the same name as netname. Key files ......... -A, B, C and D all have generated a public/private keypair with the -following command: +A, B, C and D all have their own public/private key pairs: - tincd -n company -K - - The private key is stored in '/etc/tinc/company/rsa_key.priv', the -public key is put into the host configuration file in the -'/etc/tinc/company/hosts/' directory. During key generation, tinc -automatically guesses the right filenames based on the -n option and the -Name directive in the 'tinc.conf' file (if it is available). +The private RSA key is stored in +'/usr/local/etc/tinc/company/rsa_key.priv', the private Ed25519 key is +stored in '/usr/local/etc/tinc/company/ed25519_key.priv', and the public +RSA and Ed25519 keys are put into the host configuration file in the +'/usr/local/etc/tinc/company/hosts/' directory. Starting ........ @@ -1514,7 +1761,7 @@ have started their daemons, tinc will try connecting until they are available.  -File: tinc.info, Node: Running tinc, Next: Technical information, Prev: Configuration, Up: Top +File: tinc.info, Node: Running tinc, Next: Controlling tinc, Prev: Configuration, Up: Top 5 Running tinc ************** @@ -1522,12 +1769,12 @@ File: tinc.info, Node: Running tinc, Next: Technical information, Prev: Confi If everything else is done, you can start tinc by typing the following command: - tincd -n NETNAME + tinc -n NETNAME start - Tinc will detach from the terminal and continue to run in the -background like a good daemon. If there are any problems however you -can try to increase the debug level and look in the syslog to find out -what the problems are. +Tinc will detach from the terminal and continue to run in the background +like a good daemon. If there are any problems however you can try to +increase the debug level and look in the syslog to find out what the +problems are. * Menu: @@ -1549,7 +1796,7 @@ command line options. '-c, --config=PATH' Read configuration options from the directory PATH. The default is - '/etc/tinc/NETNAME/'. + '/usr/local/etc/tinc/NETNAME/'. '-D, --no-detach' Don't fork and detach. This will also disable the automatic @@ -1559,25 +1806,15 @@ command line options. Set debug level to LEVEL. The higher the debug level, the more gets logged. Everything goes via syslog. -'-k, --kill[=SIGNAL]' - Attempt to kill a running tincd (optionally with the specified - SIGNAL instead of SIGTERM) and exit. Use it in conjunction with - the -n option to make sure you kill the right tinc daemon. Under - native Windows the optional argument is ignored, the service will - always be stopped and removed. - '-n, --net=NETNAME' Use configuration for net NETNAME. This will let tinc read all - configuration files from '/etc/tinc/NETNAME/'. Specifying . for - NETNAME is the same as not specifying any NETNAME. *Note Multiple - networks::. + configuration files from '/usr/local/etc/tinc/NETNAME/'. + Specifying . for NETNAME is the same as not specifying any + NETNAME. *Note Multiple networks::. -'-K, --generate-keys[=BITS]' - Generate public/private keypair of BITS length. If BITS is not - specified, 2048 is the default. tinc will ask where you want to - store the files, but will default to the configuration directory - (you can use the -c or -n option in combination with -K). After - that, tinc will quit. +'--pidfile=FILENAME' + Store a cookie in FILENAME which allows tinc to authenticate. If + unspecified, the default is '/usr/local/var/run/tinc.NETNAME.pid'. '-o, --option=[HOST.]KEY=VALUE' Without specifying a HOST, this will set server configuration @@ -1591,23 +1828,25 @@ command line options. shared private keys to be written to the system swap files/partitions. + This option is not supported on all platforms. + '--logfile[=FILE]' Write log entries to a file instead of to the system logging facility. If FILE is omitted, the default is - '/var/log/tinc.NETNAME.log'. + '/usr/local/var/log/tinc.NETNAME.log'. '--pidfile=FILE' - Write PID to FILE instead of '/run/tinc.NETNAME.pid'. + Write PID to FILE instead of '/usr/local/var/run/tinc.NETNAME.pid'. '--bypass-security' Disables encryption and authentication. Only useful for debugging. '-R, --chroot' Change process root directory to the directory where the config - file is located ('/etc/tinc/NETNAME/' as determined by -n/-net - option or as given by -c/-config option), for added security. The - chroot is performed after all the initialization is done, after - writing pid files and opening network sockets. + file is located ('/usr/local/etc/tinc/NETNAME/' as determined by + -n/-net option or as given by -c/-config option), for added + security. The chroot is performed after all the initialization is + done, after writing pid files and opening network sockets. This option is best used in combination with the -U/-user option described below. @@ -1620,11 +1859,14 @@ command line options. you must ensure the appropriate shell is also installed in the chroot, along with all its dependencies. + This option is not supported on all platforms. '-U, --user=USER' Switch to the given USER after initialization, at the same time as chroot is performed (see -chroot above). With this option tinc drops privileges, for added security. + This option is not supported on all platforms. + '--help' Display a short reminder of these runtime options and terminate. @@ -1653,20 +1895,6 @@ You can also send the following signals to a running tincd process: used, this will also close and reopen the log file, useful when log rotation is used. -'INT' - Temporarily increases debug level to 5. Send this signal again to - revert to the original level. - -'USR1' - Dumps the connection list to syslog. - -'USR2' - Dumps virtual network device statistics, all known nodes, edges and - subnets to syslog. - -'WINCH' - Purges all information remembered about unreachable nodes. -  File: tinc.info, Node: Debug levels, Next: Solving problems, Prev: Signals, Up: Running tinc @@ -1713,7 +1941,7 @@ directly see everything tinc logs: tincd -n NETNAME -d5 -D - If tinc does not log any error messages, then you might want to check +If tinc does not log any error messages, then you might want to check the following things: * 'tinc-up' script Does this script contain the right commands? @@ -1732,9 +1960,7 @@ the following things: it, make sure that it forwards TCP and UDP traffic to port 655 to the host running tinc. You can add 'TCPOnly = yes' to your host config file to force tinc to only use a single TCP connection, this - works through most firewalls and NATs. Since version 1.0.10, tinc - will automatically fall back to TCP if direct communication via UDP - is not possible. + works through most firewalls and NATs.  File: tinc.info, Node: Error messages, Next: Sending bug reports, Prev: Solving problems, Up: Running tinc @@ -1768,7 +1994,7 @@ high enough. 'Error reading RSA key file `rsa_key.priv': No such file or directory' - * You forgot to create a public/private keypair. + * You forgot to create a public/private key pair. * Specify the complete pathname to the private key file with the 'PrivateKeyFile' option. @@ -1825,11 +2051,16 @@ high enough. * If you see this only sporadically, it is harmless and caused by a node sending packets using an old key. + * If you see this often and another node is not reachable + anymore, then a NAT (masquerading firewall) is changing the + source address of UDP packets. You can add 'TCPOnly = yes' to + host configuration files to force all VPN traffic to go over a + TCP connection. 'Got bad/bogus/unauthorized REQUEST from foo (1.2.3.4 port 12345)' - * Node foo does not have the right public/private keypair. - Generate new keypairs and distribute them again. + * Node foo does not have the right public/private key pair. + Generate new key pairs and distribute them again. * An attacker tries to gain access to your VPN. * A network error caused corruption of metadata sent from foo. @@ -1858,9 +2089,508 @@ bugreport: ping or traceroute).  -File: tinc.info, Node: Technical information, Next: Platform specific information, Prev: Running tinc, Up: Top +File: tinc.info, Node: Controlling tinc, Next: Invitations, Prev: Running tinc, Up: Top -6 Technical information +6 Controlling tinc +****************** + +You can start, stop, control and inspect a running tincd through the +tinc command. A quick example: + + tinc -n NETNAME reload + +If tinc is started without a command, it will act as a shell; it will +display a prompt, and commands can be entered on the prompt. If tinc is +compiled with libreadline, history and command completion are available +on the prompt. One can also pipe a script containing commands through +tinc. In that case, lines starting with a # symbol will be ignored. + +* Menu: + +* tinc runtime options:: +* tinc environment variables:: +* tinc commands:: +* tinc examples:: +* tinc top:: + + +File: tinc.info, Node: tinc runtime options, Next: tinc environment variables, Up: Controlling tinc + +6.1 tinc runtime options +======================== + +'-c, --config=PATH' + Read configuration options from the directory PATH. The default is + '/usr/local/etc/tinc/NETNAME/'. + +'-n, --net=NETNAME' + Use configuration for net NETNAME. *Note Multiple networks::. + +'--pidfile=FILENAME' + Use the cookie from FILENAME to authenticate with a running tinc + daemon. If unspecified, the default is + '/usr/local/var/run/tinc.NETNAME.pid'. + +'-b, --batch' + Don't ask for anything (non-interactive mode). + +'--force' + Force some commands to work despite warnings. + +'--help' + Display a short reminder of runtime options and commands, then + terminate. + +'--version' + Output version information and exit. + + +File: tinc.info, Node: tinc environment variables, Next: tinc commands, Prev: tinc runtime options, Up: Controlling tinc + +6.2 tinc environment variables +============================== + +'NETNAME' + If no netname is specified on the command line with the '-n' + option, the value of this environment variable is used. + + +File: tinc.info, Node: tinc commands, Next: tinc examples, Prev: tinc environment variables, Up: Controlling tinc + +6.3 tinc commands +================= + +'init [NAME]' + Create initial configuration files and RSA and Ed25519 key pairs + with default length. If no NAME for this node is given, it will be + asked for. + +'get VARIABLE' + Print the current value of configuration variable VARIABLE. If + more than one variable with the same name exists, the value of each + of them will be printed on a separate line. + +'set VARIABLE VALUE' + Set configuration variable VARIABLE to the given VALUE. All + previously existing configuration variables with the same name are + removed. To set a variable for a specific host, use the notation + HOST.VARIABLE. + +'add VARIABLE VALUE' + As above, but without removing any previously existing + configuration variables. If the variable already exists with the + given value, nothing happens. + +'del VARIABLE [VALUE]' + Remove configuration variables with the same name and VALUE. If no + VALUE is given, all configuration variables with the same name will + be removed. + +'edit FILENAME' + Start an editor for the given configuration file. You do not need + to specify the full path to the file. + +'export' + Export the host configuration file of the local node to standard + output. + +'export-all' + Export all host configuration files to standard output. + +'import' + Import host configuration file(s) generated by the tinc export + command from standard input. Already existing host configuration + files are not overwritten unless the option -force is used. + +'exchange' + The same as export followed by import. + +'exchange-all' + The same as export-all followed by import. + +'invite NAME' + Prepares an invitation for a new node with the given NAME, and + prints a short invitation URL that can be used with the join + command. + +'join [URL]' + Join an existing VPN using an invitation URL created using the + invite command. If no URL is given, it will be read from standard + input. + +'start [tincd options]' + Start 'tincd', optionally with the given extra options. + +'stop' + Stop 'tincd'. + +'restart [tincd options]' + Restart 'tincd', optionally with the given extra options. + +'reload' + Partially rereads configuration files. Connections to hosts whose + host config files are removed are closed. New outgoing connections + specified in 'tinc.conf' will be made. + +'pid' + Shows the PID of the currently running 'tincd'. + +'generate-keys [BITS]' + Generate both RSA and Ed25519 key pairs (see below) and exit. tinc + will ask where you want to store the files, but will default to the + configuration directory (you can use the -c or -n option). + +'generate-ed25519-keys' + Generate public/private Ed25519 key pair and exit. + +'generate-rsa-keys [BITS]' + Generate public/private RSA key pair and exit. If BITS is omitted, + the default length will be 2048 bits. When saving keys to existing + files, tinc will not delete the old keys; you have to remove them + manually. + +'dump [reachable] nodes' + Dump a list of all known nodes in the VPN. If the reachable keyword + is used, only lists reachable nodes. + +'dump edges' + Dump a list of all known connections in the VPN. + +'dump subnets' + Dump a list of all known subnets in the VPN. + +'dump connections' + Dump a list of all meta connections with ourself. + +'dump graph | digraph' + Dump a graph of the VPN in dotty format. Nodes are colored + according to their reachability: red nodes are unreachable, orange + nodes are indirectly reachable, green nodes are directly reachable. + Black nodes are either directly or indirectly reachable, but direct + reachability has not been tried yet. + +'dump invitations' + Dump a list of outstanding invitations. The filename of the + invitation, as well as the name of the node that is being invited + is shown for each invitation. + +'info NODE | SUBNET | ADDRESS' + Show information about a particular NODE, SUBNET or ADDRESS. If an + ADDRESS is given, any matching subnet will be shown. + +'purge' + Purges all information remembered about unreachable nodes. + +'debug LEVEL' + Sets debug level to LEVEL. + +'log [LEVEL]' + Capture log messages from a running tinc daemon. An optional debug + level can be given that will be applied only for log messages sent + to tinc. + +'retry' + Forces tinc to try to connect to all uplinks immediately. Usually + tinc attempts to do this itself, but increases the time it waits + between the attempts each time it failed, and if tinc didn't + succeed to connect to an uplink the first time after it started, it + defaults to the maximum time of 15 minutes. + +'disconnect NODE' + Closes the meta connection with the given NODE. + +'top' + If tinc is compiled with libcurses support, this will display live + traffic statistics for all the known nodes, similar to the UNIX top + command. See below for more information. + +'pcap' + Dump VPN traffic going through the local tinc node in pcap-savefile + format to standard output, from where it can be redirected to a + file or piped through a program that can parse it directly, such as + tcpdump. + +'network [NETNAME]' + If NETNAME is given, switch to that network. Otherwise, display a + list of all networks for which configuration files exist. + +'fsck' + This will check the configuration files for possible problems, such + as unsafe file permissions, missing executable bit on script, + unknown and obsolete configuration variables, wrong public and/or + private keys, and so on. + + When problems are found, this will be printed on a line with + WARNING or ERROR in front of it. Most problems must be corrected + by the user itself, however in some cases (like file permissions + and missing public keys), tinc will ask if it should fix the + problem. + +'sign [FILENAME]' + Sign a file with the local node's private key. If no FILENAME is + given, the file is read from standard input. The signed file is + written to standard output. + +'verify NAME [FILENAME]' + + Check the signature of a file against a node's public key. The + NAME of the node must be given, or can be '.' to check against the + local node's public key, or '*' to allow a signature from any node + whose public key is known. If no FILENAME is given, the file is + read from standard input. If the verification is successful, a + copy of the input with the signature removed is written to standard + output, and the exit code will be zero. If the verification + failed, nothing will be written to standard output, and the exit + code will be non-zero. + + +File: tinc.info, Node: tinc examples, Next: tinc top, Prev: tinc commands, Up: Controlling tinc + +6.4 tinc examples +================= + +Examples of some commands: + + tinc -n vpn dump graph | circo -Txlib + tinc -n vpn pcap | tcpdump -r - + tinc -n vpn top + +Examples of changing the configuration using tinc: + + tinc -n vpn init foo + tinc -n vpn add Subnet 192.168.1.0/24 + tinc -n vpn add bar.Address bar.example.com + tinc -n vpn set Mode switch + tinc -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@example.com + + +File: tinc.info, Node: tinc top, Prev: tinc examples, Up: Controlling tinc + +6.5 tinc top +============ + +The top command connects to a running tinc daemon and repeatedly queries +its per-node traffic counters. It displays a list of all the known +nodes in the left-most column, and the amount of bytes and packets read +from and sent to each node in the other columns. By default, the +information is updated every second. The behaviour of the top command +can be changed using the following keys: + + + Change the interval between updates. After pressing the key, + enter the desired interval in seconds, followed by enter. + Fractional seconds are honored. Intervals lower than 0.1 seconds + are not allowed. + + + Toggle between displaying current traffic rates (in packets and + bytes per second) and cumulative traffic (total packets and bytes + since the tinc daemon started). + + + Sort the list of nodes by name. + + + Sort the list of nodes by incoming amount of bytes. + + + Sort the list of nodes by incoming amount of packets. + + + Sort the list of nodes by outgoing amount of bytes. + + + Sort the list of nodes by outgoing amount of packets. + + + Sort the list of nodes by sum of incoming and outgoing amount of + bytes. + + + Sort the list of nodes by sum of incoming and outgoing amount of + packets. + + + Show amount of traffic in bytes. + + + Show amount of traffic in kilobytes. + + + Show amount of traffic in megabytes. + + + Show amount of traffic in gigabytes. + + + Quit. + + +File: tinc.info, Node: Invitations, Next: Technical information, Prev: Controlling tinc, Up: Top + +7 Invitations +************* + +Invitations are an easy way to add new nodes to an existing VPN. +Invitations can be created on an existing node using the 'tinc invite' +command, which generates a relatively short URL which can be given to +someone else, who uses the 'tinc join' command to automatically set up +tinc so it can connect to the inviting node. The next sections describe +how invitations actually work, and how to further automate the +invitations. + +* Menu: + +* How invitations work:: +* Invitation file format:: +* Writing an invitation-created script:: + + +File: tinc.info, Node: How invitations work, Next: Invitation file format, Up: Invitations + +7.1 How invitations work +======================== + +When an invitation is created on a node (which from now on we will call +the server) using the 'tinc invite' command, an invitation file is +created that contains all the information necessary for the invitee +(which we will call the client) to create its configuration files. The +invitation file is stays on the server, but a URL is generated that has +enough information for the client to contact the server and to retrieve +the invitation file. The whole URL is around 80 characters long and +looks like this: + + server.example.org:12345/cW1NhLHS-1WPFlcFio8ztYHvewTTKYZp8BjEKg3vbMtDz7w4 + +It is composed of four parts: + + hostname : port / keyhash cookie + +The hostname and port tell the client how to reach the tinc daemon on +the server. The part after the slash looks like one blob, but is +composed of two parts. The keyhash is the hash of the public key of the +server. The cookie is a shared secret that identifies the client to the +server. + +When the client connects to the server in order to join the VPN, the +client and server will exchange temporary public keys. The client +verifies that the hash of the server's public key matches the keyhash +from the invitation URL. If not, it will immediately exit with an error. +Otherwise, an ECDH exchange will happen so the client and server can +communicate privately with each other. The client will then present the +cookie to the server. The server uses this to look up the corresponding +invitation file it generated earlier. If it exists, it will send the +invitation file to the client. The client will also create a permanent +public key, and send it to the server. After the exchange is completed, +the connection is broken. The server creates a host config file for the +client containing the client's permanent public key, and the client +creates tinc.conf, host config files and possibly a tinc-up script based +on the information in the invitation file. + +It is important that the invitation URL is kept secret until it is used; +if another person gets a copy of the invitation URL before the real +client runs the 'tinc join' command, then that other person can try to +join the VPN. + + +File: tinc.info, Node: Invitation file format, Next: Writing an invitation-created script, Prev: How invitations work, Up: Invitations + +7.2 Invitation file format +========================== + +The contents of an invitation file that is generated by the 'tinc +invite' command looks like this: + + Name = client + Netname = vpn + ConnectTo = server + #-------------------------------------# + Name = server + Ed25519PublicKey = augbnwegoij123587... + Address = server.example.com + +The file is basically a concatenation of several host config blocks. +Each host config block starts with 'Name = ...'. Lines that look like +'#---#' are not important, it just makes it easier for humans to read +the file. However, the first line of an invitation file _must_ always +start with 'Name = ...'. + +The first host config block is always the one representing the invitee. +So the first Name statement determines the name that the invitee will +get. From the first block, the 'tinc.conf' and 'hosts/client' files +will be generated; the 'tinc join' command on the client will +automatically separate statements based on whether they should be in +'tinc.conf' or in a host config file. Some statements are special and +are treated differently: + +Netname = + This is a hint to the invitee which netname to use for the VPN. It + is used if the invitee did not already specify a netname, and if + there is no pre-existing configuration with the same netname. + +Ifconfig = + This is a hint for generating a 'tinc-up' script. If an address is + specified, a command will be added to 'tinc-up' so the VPN + interface will be configured to have the given address. If it is + the word 'dhcp', a command will be added to start a DHCP client on + the VPN interface. If it is the word 'dhcpv6', it will be a DHCPv6 + client. If it is 'slaac', then it will add commands to enable IPv6 + stateless address autoconfiguration. It is also possible to + specify a MAC address, in which case a command will be added to set + the MAC address of the VPN interface. + + The exact commands added to the 'tinc-up' script depends on the + operating system the client is using. Multiple Ifconfig statements + can be specified, however one should only use one Ifconfig + statement per address family. + +Route = [] + This is a hint for generating a 'tinc-up' script. Route statements + are similar to Ifconfig statements, but add routes instead of + addresses. These only allow IPv4 and IPv6 routes. If no gateway + address is specified, the route is directed to the VPN interface. + In general, a gateway is only necessary when running tinc in switch + mode. + +Subsequent host config blocks are copied verbatim into their respective +files in 'hosts/'. The invitation file generated by 'tinc invite' will +normally only contain two blocks; one for the client and one for the +server. + + +File: tinc.info, Node: Writing an invitation-created script, Prev: Invitation file format, Up: Invitations + +7.3 Writing an invitation-created script +======================================== + +When an invitation is generated, the 'invitation-created' script is +called (if it exists) right after the invitation file is written, but +before the URL has been written to stdout. This allows one to change +the invitation file automatically before the invitation URL is passed to +the invitee. Here is an example shell script that approximately +recreates the default invitation file: + + #!/bin/sh + + cat >$INVITATION_FILE <>$INVITATION_FILE + +You can add more ConnectTo statements, and change 'tinc export' to 'tinc +export-all' for example. But you can also use the script to +automatically hand out a Subnet to the invitee. Note that the script +doesn't have to be a shell script, you can use any language, it just has +to be executable. + + +File: tinc.info, Node: Technical information, Next: Platform specific information, Prev: Invitations, Up: Top + +8 Technical information *********************** * Menu: @@ -1872,7 +2602,7 @@ File: tinc.info, Node: Technical information, Next: Platform specific informat  File: tinc.info, Node: The connection, Next: The meta-protocol, Up: Technical information -6.1 The connection +8.1 The connection ================== Tinc is a daemon that takes VPN data and transmit that to another host @@ -1886,7 +2616,7 @@ computer over the existing Internet infrastructure.  File: tinc.info, Node: The UDP tunnel, Next: The meta-connection, Up: The connection -6.1.1 The UDP tunnel +8.1.1 The UDP tunnel -------------------- The data itself is read from a character device file, the so-called @@ -1898,43 +2628,43 @@ are point-to-point devices which can only handle IPv4 and/or IPv6 packets, and 'tap' style, which are Ethernet devices and handle complete Ethernet frames. - So when tinc reads an Ethernet frame from the device, it determines -its type. When tinc is in its default routing mode, it can handle IPv4 -and IPv6 packets. Depending on the Subnet lines, it will send the -packets off to their destination IP address. In the 'switch' and 'hub' -mode, tinc will use broadcasts and MAC address discovery to deduce the +So when tinc reads an Ethernet frame from the device, it determines its +type. When tinc is in it's default routing mode, it can handle IPv4 and +IPv6 packets. Depending on the Subnet lines, it will send the packets +off to their destination IP address. In the 'switch' and 'hub' mode, +tinc will use broadcasts and MAC address discovery to deduce the destination of the packets. Since the latter modes only depend on the link layer information, any protocol that runs over Ethernet is supported (for instance IPX and Appletalk). However, only 'tap' style devices provide this information. - After the destination has been determined, the packet will be -compressed (optionally), a sequence number will be added to the packet, -the packet will then be encrypted and a message authentication code will -be appended. +After the destination has been determined, the packet will be compressed +(optionally), a sequence number will be added to the packet, the packet +will then be encrypted and a message authentication code will be +appended. - When that is done, time has come to actually transport the packet to -the destination computer. We do this by sending the packet over an UDP +When that is done, time has come to actually transport the packet to the +destination computer. We do this by sending the packet over an UDP connection to the destination host. This is called _encapsulating_, the VPN packet (though now encrypted) is encapsulated in another IP datagram. - When the destination receives this packet, the same thing happens, -only in reverse. So it checks the message authentication code, decrypts -the contents of the UDP datagram, checks the sequence number and writes -the decrypted information to its own virtual network device. +When the destination receives this packet, the same thing happens, only +in reverse. So it checks the message authentication code, decrypts the +contents of the UDP datagram, checks the sequence number and writes the +decrypted information to its own virtual network device. - If the virtual network device is a 'tun' device (a point-to-point +If the virtual network device is a 'tun' device (a point-to-point tunnel), there is no problem for the kernel to accept a packet. However, if it is a 'tap' device (this is the only available type on FreeBSD), the destination MAC address must match that of the virtual -network interface. If tinc is in its default routing mode, ARP does not -work, so the correct destination MAC can not be known by the sending +network interface. If tinc is in it's default routing mode, ARP does +not work, so the correct destination MAC can not be known by the sending host. Tinc solves this by letting the receiving end detect the MAC address of its own virtual network interface and overwriting the destination MAC address of the received packet. - In switch or hub modes ARP does work so the sender already knows the +In switch or hub modes ARP does work so the sender already knows the correct destination MAC address. In those modes every interface should have a unique MAC address, so make sure they are not the same. Because switch and hub modes rely on MAC addresses to function correctly, these @@ -1944,28 +2674,28 @@ a 'tap' style virtual network device: NetBSD, Darwin and Solaris.  File: tinc.info, Node: The meta-connection, Prev: The UDP tunnel, Up: The connection -6.1.2 The meta-connection +8.1.2 The meta-connection ------------------------- Having only a UDP connection available is not enough. Though suitable for transmitting data, we want to be able to reliably send other information, such as routing and session key information to somebody. - TCP is a better alternative, because it already contains protection +TCP is a better alternative, because it already contains protection against information being lost, unlike UDP. - So we establish two connections. One for the encrypted VPN data, and +So we establish two connections. One for the encrypted VPN data, and one for other information, the meta-data. Hence, we call the second connection the meta-connection. We can now be sure that the meta-information doesn't get lost on the way to another computer. - Like with any communication, we must have a protocol, so that -everybody knows what everything stands for, and how she should react. -Because we have two connections, we also have two protocols. The -protocol used for the UDP data is the "data-protocol," the other one is -the "meta-protocol." +Like with any communication, we must have a protocol, so that everybody +knows what everything stands for, and how she should react. Because we +have two connections, we also have two protocols. The protocol used for +the UDP data is the "data-protocol," the other one is the +"meta-protocol." - The reason we don't use TCP for both protocols is that UDP is much +The reason we don't use TCP for both protocols is that UDP is much better for encapsulation, even while it is less reliable. The real problem is that when TCP would be used to encapsulate a TCP stream that's on the private network, for every packet sent there would be @@ -1976,24 +2706,24 @@ re-sending packets.  File: tinc.info, Node: The meta-protocol, Next: Security, Prev: The connection, Up: Technical information -6.2 The meta-protocol +8.2 The meta-protocol ===================== The meta protocol is used to tie all tinc daemons together, and exchange information about which tinc daemon serves which virtual subnet. - The meta protocol consists of requests that can be sent to the other +The meta protocol consists of requests that can be sent to the other side. Each request has a unique number and several parameters. All requests are represented in the standard ASCII character set. It is possible to use tools such as telnet or netcat to connect to a tinc daemon started with the -bypass-security option and to read and write requests by hand, provided that one understands the numeric codes sent. - The authentication scheme is described in *note Authentication -protocol::. After a successful authentication, the server and the -client will exchange all the information about other tinc daemons and -subnets they know of, so that both sides (and all the other tinc daemons -behind them) have their information synchronised. +The authentication scheme is described in *note Security::. After a +successful authentication, the server and the client will exchange all +the information about other tinc daemons and subnets they know of, so +that both sides (and all the other tinc daemons behind them) have their +information synchronised. message ------------------------------------------------------------------ @@ -2011,13 +2741,13 @@ behind them) have their information synchronised. +------------------> owner of this subnet ------------------------------------------------------------------ - The ADD_EDGE messages are to inform other tinc daemons that a -connection between two nodes exist. The address of the destination node -is available so that VPN packets can be sent directly to that node. +The ADD_EDGE messages are to inform other tinc daemons that a connection +between two nodes exist. The address of the destination node is +available so that VPN packets can be sent directly to that node. - The ADD_SUBNET messages inform other tinc daemons that certain -subnets belong to certain nodes. tinc will use it to determine to which -node a VPN packet has to be sent. +The ADD_SUBNET messages inform other tinc daemons that certain subnets +belong to certain nodes. tinc will use it to determine to which node a +VPN packet has to be sent. message ------------------------------------------------------------------ @@ -2031,10 +2761,10 @@ node a VPN packet has to be sent. +------------------> owner of this subnet ------------------------------------------------------------------ - In case a connection between two daemons is closed or broken, -DEL_EDGE messages are sent to inform the other daemons of that fact. -Each daemon will calculate a new route to the the daemons, or mark them -unreachable if there isn't any. +In case a connection between two daemons is closed or broken, DEL_EDGE +messages are sent to inform the other daemons of that fact. Each daemon +will calculate a new route to the the daemons, or mark them unreachable +if there isn't any. message ------------------------------------------------------------------ @@ -2054,20 +2784,20 @@ unreachable if there isn't any. +--> daemon that has changed it's packet key ------------------------------------------------------------------ - The keys used to encrypt VPN packets are not sent out directly. This -is because it would generate a lot of traffic on VPNs with many daemons, +The keys used to encrypt VPN packets are not sent out directly. This is +because it would generate a lot of traffic on VPNs with many daemons, and chances are that not every tinc daemon will ever send a packet to every other daemon. Instead, if a daemon needs a key it sends a request for it via the meta connection of the nearest hop in the direction of the destination. - daemon message + daemon message ------------------------------------------------------------------ - origin PING - dest. PONG + origin PING + dest. PONG ------------------------------------------------------------------ - There is also a mechanism to check if hosts are still alive. Since +There is also a mechanism to check if hosts are still alive. Since network failures or a crash can cause a daemon to be killed without properly shutting down the TCP connection, this is necessary to keep an up to date connection list. PINGs are sent at regular intervals, except @@ -2076,12 +2806,12 @@ data) is added with each PING and PONG message, to make sure that long sequences of PING/PONG messages without any other traffic won't result in known plaintext. - This basically covers what is sent over the meta connection by tinc. +This basically covers what is sent over the meta connection by tinc.  File: tinc.info, Node: Security, Prev: The meta-protocol, Up: Technical information -6.3 Security +8.3 Security ============ Tinc got its name from "TINC," short for _There Is No Cabal_; the @@ -2089,33 +2819,33 @@ alleged Cabal was/is an organisation that was said to keep an eye on the entire Internet. As this is exactly what you _don't_ want, we named the tinc project after TINC. - But in order to be "immune" to eavesdropping, you'll have to encrypt +But in order to be "immune" to eavesdropping, you'll have to encrypt your data. Because tinc is a _Secure_ VPN (SVPN) daemon, it does -exactly that: encrypt. Tinc by default uses blowfish encryption with -128 bit keys in CBC mode, 32 bit sequence numbers and 4 byte long -message authentication codes to make sure eavesdroppers cannot get and -cannot change any information at all from the packets they can -intercept. The encryption algorithm and message authentication -algorithm can be changed in the configuration. The length of the -message authentication codes is also adjustable. The length of the key -for the encryption algorithm is always the default length used by -LibreSSL/OpenSSL. +exactly that: encrypt. However, encryption in itself does not prevent +an attacker from modifying the encrypted data. Therefore, tinc also +authenticates the data. Finally, tinc uses sequence numbers (which +themselves are also authenticated) to prevent an attacker from replaying +valid packets. + +Since version 1.1pre3, tinc has two protocols used to protect your data; +the legacy protocol, and the new Simple Peer-to-Peer Security (SPTPS) +protocol. The SPTPS protocol is designed to address some weaknesses in +the legacy protocol. The new authentication protocol is used when two +nodes connect to each other that both have the ExperimentalProtocol +option set to yes, otherwise the legacy protocol will be used. * Menu: -* Authentication protocol:: +* Legacy authentication protocol:: +* Simple Peer-to-Peer Security:: * Encryption of network packets:: * Security issues::  -File: tinc.info, Node: Authentication protocol, Next: Encryption of network packets, Up: Security +File: tinc.info, Node: Legacy authentication protocol, Next: Simple Peer-to-Peer Security, Up: Security -6.3.1 Authentication protocol ------------------------------ - -A new scheme for authentication in tinc has been devised, which offers -some improvements over the protocol used in 1.0pre2 and 1.0pre3. -Explanation is below. +8.3.1 Legacy authentication protocol +------------------------------------ daemon message -------------------------------------------------------------------------- @@ -2123,28 +2853,46 @@ Explanation is below. server - client ID client 12 - | +---> version - +-------> name of tinc daemon + client ID client 17.2 + | | +-> minor protocol version + | +----> major protocol version + +--------> name of tinc daemon - server ID server 12 - | +---> version - +-------> name of tinc daemon + server ID server 17.2 + | | +-> minor protocol version + | +----> major protocol version + +--------> name of tinc daemon - client META_KEY 5f0823a93e35b69e...7086ec7866ce582b - \_________________________________/ - +-> RSAKEYLEN bits totally random string S1, - encrypted with server's public RSA key + client META_KEY 94 64 0 0 5f0823a93e35b69e...7086ec7866ce582b + | | | | \_________________________________/ + | | | | +-> RSAKEYLEN bits totally random string S1, + | | | | encrypted with server's public RSA key + | | | +-> compression level + | | +---> MAC length + | +------> digest algorithm NID + +---------> cipher algorithm NID - server META_KEY 6ab9c1640388f8f0...45d1a07f8a672630 - \_________________________________/ - +-> RSAKEYLEN bits totally random string S2, - encrypted with client's public RSA key + server META_KEY 94 64 0 0 6ab9c1640388f8f0...45d1a07f8a672630 + | | | | \_________________________________/ + | | | | +-> RSAKEYLEN bits totally random string S2, + | | | | encrypted with client's public RSA key + | | | +-> compression level + | | +---> MAC length + | +------> digest algorithm NID + +---------> cipher algorithm NID + -------------------------------------------------------------------------- - From now on: - - the client will symmetrically encrypt outgoing traffic using S1 - - the server will symmetrically encrypt outgoing traffic using S2 +The protocol allows each side to specify encryption algorithms and +parameters, but in practice they are always fixed, since older versions +of tinc did not allow them to be different from the default values. The +cipher is always Blowfish in OFB mode, the digest is SHA1, but the MAC +length is zero and no compression is used. +From now on: + * the client will symmetrically encrypt outgoing traffic using S1 + * the server will symmetrically encrypt outgoing traffic using S2 + + -------------------------------------------------------------------------- client CHALLENGE da02add1817c1920989ba6ae2a49cecbda0 \_________________________________/ +-> CHALLEN bits totally random string H1 @@ -2164,74 +2912,204 @@ Explanation is below. client ACK 655 123 0 | | +-> options - | +----> estimated weight - +--------> listening port of client + | +----> estimated weight + +--------> listening port of client server ACK 655 321 0 | | +-> options - | +----> estimated weight - +--------> listening port of server + | +----> estimated weight + +--------> listening port of server -------------------------------------------------------------------------- - This new scheme has several improvements, both in efficiency and -security. - - First of all, the server sends exactly the same kind of messages over -the wire as the client. The previous versions of tinc first -authenticated the client, and then the server. This scheme even allows -both sides to send their messages simultaneously, there is no need to -wait for the other to send something first. This means that any -calculations that need to be done upon sending or receiving a message -can also be done in parallel. This is especially important when doing -RSA encryption/decryption. Given that these calculations are the main -part of the CPU time spent for the authentication, speed is improved by -a factor 2. - - Second, only one RSA encrypted message is sent instead of two. This -reduces the amount of information attackers can see (and thus use for a -cryptographic attack). It also improves speed by a factor two, making -the total speedup a factor 4. - - Third, and most important: The symmetric cipher keys are exchanged -first, the challenge is done afterwards. In the previous authentication -scheme, because a man-in-the-middle could pass the challenge/chal_reply -phase (by just copying the messages between the two real tinc daemons), -but no information was exchanged that was really needed to read the rest -of the messages, the challenge/chal_reply phase was of no real use. The -man-in-the-middle was only stopped by the fact that only after the ACK -messages were encrypted with the symmetric cipher. Potentially, it -could even send it's own symmetric key to the server (if it knew the -server's public key) and read some of the metadata the server would send -it (it was impossible for the mitm to read actual network packets -though). The new scheme however prevents this. - - This new scheme makes sure that first of all, symmetric keys are -exchanged. The rest of the messages are then encrypted with the -symmetric cipher. Then, each side can only read received messages if -they have their private key. The challenge is there to let the other -side know that the private key is really known, because a challenge -reply can only be sent back if the challenge is decrypted correctly, and -that can only be done with knowledge of the private key. - - Fourth: the first thing that is sent via the symmetric cipher -encrypted connection is a totally random string, so that there is no -known plaintext (for an attacker) in the beginning of the encrypted -stream. +This legacy authentication protocol has several weaknesses, pointed out +by security export Peter Gutmann. First, data is encrypted with RSA +without padding. Padding schemes are designed to prevent attacks when +the size of the plaintext is not equal to the size of the RSA key. Tinc +always encrypts random nonces that have the same size as the RSA key, so +we do not believe this leads to a break of the security. There might be +timing or other side-channel attacks against RSA encryption and +decryption, tinc does not employ any protection against those. +Furthermore, both sides send identical messages to each other, there is +no distinction between server and client, which could make a MITM attack +easier. However, no exploit is known in which a third party who is not +already trusted by other nodes in the VPN could gain access. Finally, +the RSA keys are used to directly encrypt the session keys, which means +that if the RSA keys are compromised, it is possible to decrypt all +previous VPN traffic. In other words, the legacy protocol does not +provide perfect forward secrecy.  -File: tinc.info, Node: Encryption of network packets, Next: Security issues, Prev: Authentication protocol, Up: Security +File: tinc.info, Node: Simple Peer-to-Peer Security, Next: Encryption of network packets, Prev: Legacy authentication protocol, Up: Security -6.3.2 Encryption of network packets +8.3.2 Simple Peer-to-Peer Security +---------------------------------- + +The SPTPS protocol is designed to address the weaknesses in the legacy +protocol. SPTPS is based on TLS 1.2, but has been simplified: there is +no support for exchanging public keys, and there is no cipher suite +negotiation. Instead, SPTPS always uses a very strong cipher suite: +peers authenticate each other using 521 bits ECC keys, Diffie-Hellman +using ephemeral 521 bits ECC keys is used to provide perfect forward +secrecy (PFS), AES-256-CTR is used for encryption, and HMAC-SHA-256 for +message authentication. + +Similar to TLS, messages are split up in records. A complete logical +record contains the following information: + + * uint32_t seqno (network byte order) + * uint16_t length (network byte order) + * uint8_t type + * opaque data[length] + * opaque hmac[HMAC_SIZE] (HMAC over all preceding fields) + +Depending on whether SPTPS records are sent via TCP or UDP, either the +seqno or the length field is omitted on the wire (but they are still +included in the calculation of the HMAC); for TCP packets are guaranteed +to arrive in-order so we can infer the seqno, but packets can be split +or merged, so we still need the length field to determine the boundaries +between records; for UDP packets we know that there is exactly one +record per packet, and we know the length of a packet, but packets can +be dropped, duplicated and/or reordered, so we need to include the +seqno. + +The type field is used to distinguish between application records or +handshake records. Types 0 to 127 are application records, type 128 is +a handshake record, and types 129 to 255 are reserved. + +Before the initial handshake, no fields are encrypted, and the HMAC +field is not present. After the authentication handshake, the length +(if present), type and data fields are encrypted, and the HMAC field is +present. For UDP packets, the seqno field is not encrypted, as it is +used to determine the value of the counter used for encryption. + +The authentication consists of an exchange of Key EXchange, SIGnature +and ACKnowledge messages, transmitted using type 128 records. + +Overview: + + Initiator Responder + --------------------- + KEX -> + <- KEX + SIG -> + <- SIG + + ...encrypt and HMAC using session keys from now on... + + App -> + <- App + ... + ... + + ...key renegotiation starts here... + + KEX -> + <- KEX + SIG -> + <- SIG + ACK -> + <- ACK + + ...encrypt and HMAC using new session keys from now on... + + App -> + <- App + ... + ... + --------------------- + +Note that the responder does not need to wait before it receives the +first KEX message, it can immediately send its own once it has accepted +an incoming connection. + +Key EXchange message: + + * uint8_t kex_version (always 0 in this version of SPTPS) + * opaque nonce[32] (random number) + * opaque ecdh_key[ECDH_SIZE] + +SIGnature message: + + * opaque ecdsa_signature[ECDSA_SIZE] + +ACKnowledge message: + + * empty (only sent after key renegotiation) + +Remarks: + + * At the start, both peers generate a random nonce and an Elliptic + Curve public key and send it to the other in the KEX message. + * After receiving the other's KEX message, both KEX messages are + concatenated (see below), and the result is signed using ECDSA. The + result is sent to the other. + * After receiving the other's SIG message, the signature is verified. + If it is correct, the shared secret is calculated from the public + keys exchanged in the KEX message using the Elliptic Curve + Diffie-Helman algorithm. + * The shared secret key is expanded using a PRF. Both nonces and the + application specific label are also used as input for the PRF. + * An ACK message is sent only when doing key renegotiation, and is + sent using the old encryption keys. + * The expanded key is used to key the encryption and HMAC algorithms. + +The signature is calculated over this string: + + * uint8_t initiator (0 = local peer, 1 = remote peer is initiator) + * opaque remote_kex_message[1 + 32 + ECDH_SIZE] + * opaque local_kex_message[1 + 32 + ECDH_SIZE] + * opaque label[label_length] + +The PRF is calculated as follows: + + * A HMAC using SHA512 is used, the shared secret is used as the key. + * For each block of 64 bytes, a HMAC is calculated. For block n: + hmac[n] = HMAC_SHA512(hmac[n - 1] + seed) + * For the first block (n = 1), hmac[0] is given by HMAC_SHA512(zeroes + + seed), where zeroes is a block of 64 zero bytes. + +The seed is as follows: + + * const char[13] "key expansion" + * opaque responder_nonce[32] + * opaque initiator_nonce[32] + * opaque label[label_length] + +The expanded key is used as follows: + + * opaque responder_cipher_key[CIPHER_KEYSIZE] + * opaque responder_digest_key[DIGEST_KEYSIZE] + * opaque initiator_cipher_key[CIPHER_KEYSIZE] + * opaque initiator_digest_key[DIGEST_KEYSIZE] + +Where initiator_cipher_key is the key used by session initiator to +encrypt messages sent to the responder. + +When using 256 bits Ed25519 keys, the AES-256-CTR cipher and +HMAC-SHA-256 digest algorithm, the sizes are as follows: + + ECDH_SIZE: 32 (= 256/8) + ECDSA_SIZE: 64 (= 2 * 256/8) + CIPHER_KEYSIZE: 48 (= 256/8 + 128/8) + DIGEST_KEYSIZE: 32 (= 256/8) + +Note that the cipher key also includes the initial value for the +counter. + + +File: tinc.info, Node: Encryption of network packets, Next: Security issues, Prev: Simple Peer-to-Peer Security, Up: Security + +8.3.3 Encryption of network packets ----------------------------------- A data packet can only be sent if the encryption key is known to both parties, and the connection is activated. If the encryption key is not known, a request is sent to the destination using the meta connection to -retrieve it. The packet is stored in a queue while waiting for the key -to arrive. +retrieve it. - The UDP packet containing the network packet from the VPN has the -following layout: +The UDP packets can be either encrypted with the legacy protocol or with +SPTPS. In case of the legacy protocol, the UDP packet containing the +network packet from the VPN has the following layout: ... | IP header | UDP header | seqno | VPN packet | MAC | UDP trailer \___________________/\_____/ @@ -2239,18 +3117,39 @@ following layout: V +---> digest algorithm Encrypted with symmetric cipher - So, the entire VPN packet is encrypted using a symmetric cipher, +So, the entire VPN packet is encrypted using a symmetric cipher, including a 32 bits sequence number that is added in front of the actual VPN packet, to act as a unique IV for each packet and to prevent replay attacks. A message authentication code is added to the UDP packet to -prevent alteration of packets. By default the first 4 bytes of the -digest are used for this, but this can be changed using the MACLength -configuration variable. +prevent alteration of packets. Tinc by default encrypts network packets +using Blowfish with 128 bit keys in CBC mode and uses 4 byte long +message authentication codes to make sure eavesdroppers cannot get and +cannot change any information at all from the packets they can +intercept. The encryption algorithm and message authentication +algorithm can be changed in the configuration. The length of the +message authentication codes is also adjustable. The length of the key +for the encryption algorithm is always the default length used by +LibreSSL/OpenSSL. + +The SPTPS protocol is described in *note Simple Peer-to-Peer Security::. +For comparison, this is how SPTPS UDP packets look: + + ... | IP header | UDP header | seqno | type | VPN packet | MAC | UDP trailer + \__________________/\_____/ + | | + V +---> digest algorithm + Encrypted with symmetric cipher + +The difference is that the seqno is not encrypted, since the encryption +cipher is used in CTR mode, and therefore the seqno must be known before +the packet can be decrypted. Furthermore, the MAC is never truncated. +The SPTPS protocol always uses the AES-256-CTR cipher and HMAC-SHA-256 +digest, this cannot be changed.  File: tinc.info, Node: Security issues, Prev: Encryption of network packets, Up: Security -6.3.3 Security issues +8.3.4 Security issues --------------------- In August 2000, we discovered the existence of a security hole in all @@ -2260,22 +3159,40 @@ authentication scheme to make tinc as secure as possible. The current version uses the LibreSSL or OpenSSL library and uses strong authentication with RSA keys. - On the 29th of December 2001, Jerome Etienne posted a security -analysis of tinc 1.0pre4. Due to a lack of sequence numbers and a -message authentication code for each packet, an attacker could possibly -disrupt certain network services or launch a denial of service attack by +On the 29th of December 2001, Jerome Etienne posted a security analysis +of tinc 1.0pre4. Due to a lack of sequence numbers and a message +authentication code for each packet, an attacker could possibly disrupt +certain network services or launch a denial of service attack by replaying intercepted packets. The current version adds sequence numbers and message authentication codes to prevent such attacks. - On the 15th of September 2003, Peter Gutmann posted a security -analysis of tinc 1.0.1. He argues that the 32 bit sequence number used -by tinc is not a good IV, that tinc's default length of 4 bytes for the -MAC is too short, and he doesn't like tinc's use of RSA during -authentication. We do not know of a security hole in this version of -tinc, but tinc's security is not as strong as TLS or IPsec. We will -address these issues in tinc 2.0. +On the 15th of September 2003, Peter Gutmann posted a security analysis +of tinc 1.0.1. He argues that the 32 bit sequence number used by tinc +is not a good IV, that tinc's default length of 4 bytes for the MAC is +too short, and he doesn't like tinc's use of RSA during authentication. +We do not know of a security hole in the legacy protocol of tinc, but it +is not as strong as TLS or IPsec. - Cryptography is a hard thing to get right. We cannot make any +The Sweet32 attack affects versions of tinc prior to 1.0.30. + +On September 6th, 2018, Michael Yonly contacted us and provided +proof-of-concept code that allowed a remote attacker to create an +authenticated, one-way connection with a node, and also that there was a +possibility for a man-in-the-middle to force UDP packets from a node to +be sent in plaintext. The first issue was trivial to exploit on tinc +versions prior to 1.0.30, but the changes in 1.0.30 to mitigate the +Sweet32 attack made this weakness much harder to exploit. These issues +have been fixed in tinc 1.0.35. + +This version of tinc comes with an improved protocol, called Simple +Peer-to-Peer Security (SPTPS), which aims to be as strong as TLS with +one of the strongest cipher suites. None of the above security issues +affected SPTPS. However, be aware that SPTPS is only used between nodes +running tinc 1.1pre* or later, and in a VPN with nodes running different +versions, the security might only be as good as that of the oldest +version. + +Cryptography is a hard thing to get right. We cannot make any guarantees. Time, review and feedback are the only things that can prove the security of any cryptographic product. If you wish to review tinc or give us feedback, you are strongly encouraged to do so. @@ -2283,7 +3200,7 @@ tinc or give us feedback, you are strongly encouraged to do so.  File: tinc.info, Node: Platform specific information, Next: About us, Prev: Technical information, Up: Top -7 Platform specific information +9 Platform specific information ******************************* * Menu: @@ -2295,7 +3212,7 @@ File: tinc.info, Node: Platform specific information, Next: About us, Prev: T  File: tinc.info, Node: Interface configuration, Next: Routes, Up: Platform specific information -7.1 Interface configuration +9.1 Interface configuration =========================== When configuring an interface, one normally assigns it an address and a @@ -2307,40 +3224,40 @@ to that interface. Because all packets for the entire VPN should go to the virtual network interface used by tinc, the netmask should be such that it encompasses the entire VPN. - For IPv4 addresses: +For IPv4 addresses: -Linux 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -Linux iproute2 'ip addr add' ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE -FreeBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -OpenBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -NetBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -Solaris 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -Darwin (Mac OS X) 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK -Windows 'netsh interface ip set address' INTERFACE 'static' ADDRESS NETMASK +Linux 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +Linux iproute2 'ip addr add' ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE +FreeBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +OpenBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +NetBSD 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +Solaris 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +Darwin (MacOS/X) 'ifconfig' INTERFACE ADDRESS 'netmask' NETMASK +Windows 'netsh interface ip set address' INTERFACE 'static' ADDRESS NETMASK - For IPv6 addresses: +For IPv6 addresses: -Linux 'ifconfig' INTERFACE 'add' ADDRESS'/'PREFIXLENGTH -FreeBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH -OpenBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH -NetBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH -Solaris 'ifconfig' INTERFACE 'inet6 plumb up' - 'ifconfig' INTERFACE 'inet6 addif' ADDRESS ADDRESS -Darwin (Mac OS X) 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH -Windows 'netsh interface ipv6 add address' INTERFACE 'static' ADDRESS/PREFIXLENGTH +Linux 'ifconfig' INTERFACE 'add' ADDRESS'/'PREFIXLENGTH +FreeBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH +OpenBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH +NetBSD 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH +Solaris 'ifconfig' INTERFACE 'inet6 plumb up' + 'ifconfig' INTERFACE 'inet6 addif' ADDRESS ADDRESS +Darwin (MacOS/X) 'ifconfig' INTERFACE 'inet6' ADDRESS 'prefixlen' PREFIXLENGTH +Windows 'netsh interface ipv6 add address' INTERFACE 'static' ADDRESS/PREFIXLENGTH - On Linux, it is possible to create a persistent tun/tap interface -which will continue to exist even if tinc quit, although this is -normally not required. It can be useful to set up a tun/tap interface -owned by a non-root user, so tinc can be started without needing any -root privileges at all. +On Linux, it is possible to create a persistent tun/tap interface which +will continue to exist even if tinc quit, although this is normally not +required. It can be useful to set up a tun/tap interface owned by a +non-root user, so tinc can be started without needing any root +privileges at all. -Linux 'ip tuntap add dev' INTERFACE 'mode' TUN|TAP 'user' USERNAME +Linux 'ip tuntap add dev' INTERFACE 'mode' TUN|TAP 'user' USERNAME  File: tinc.info, Node: Routes, Next: Automatically starting tinc, Prev: Interface configuration, Up: Platform specific information -7.2 Routes +9.2 Routes ========== In some cases it might be necessary to add more routes to the virtual @@ -2350,33 +3267,33 @@ another way is to specify the (local) address that is assigned to that interface (LOCAL_ADDRESS). The former way is unambiguous and therefore preferable, but not all platforms support this. - Adding routes to IPv4 subnets: +Adding routes to IPv4 subnets: -Linux 'route add -net' NETWORK_ADDRESS 'netmask' NETMASK INTERFACE -Linux iproute2 'ip route add' NETWORK_ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE -FreeBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS -OpenBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS -NetBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS -Solaris 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS '-interface' -Darwin (Mac OS X) 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH '-interface' INTERFACE -Windows 'netsh routing ip add persistentroute' NETWORK_ADDRESS NETMASK INTERFACE - LOCAL_ADDRESS +Linux 'route add -net' NETWORK_ADDRESS 'netmask' NETMASK INTERFACE +Linux iproute2 'ip route add' NETWORK_ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE +FreeBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS +OpenBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS +NetBSD 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS +Solaris 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS '-interface' +Darwin (MacOS/X) 'route add' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS +Windows 'netsh routing ip add persistentroute' NETWORK_ADDRESS NETMASK INTERFACE + LOCAL_ADDRESS - Adding routes to IPv6 subnets: +Adding routes to IPv6 subnets: -Linux 'route add -A inet6' NETWORK_ADDRESS'/'PREFIXLENGTH INTERFACE -Linux iproute2 'ip route add' NETWORK_ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE -FreeBSD 'route add -inet6' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS -OpenBSD 'route add -inet6' NETWORK_ADDRESS LOCAL_ADDRESS '-prefixlen' PREFIXLENGTH -NetBSD 'route add -inet6' NETWORK_ADDRESS LOCAL_ADDRESS '-prefixlen' PREFIXLENGTH -Solaris 'route add -inet6' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS '-interface' -Darwin (Mac OS X) 'route add -inet6' NETWORK_ADDRESS'/'PREFIXLENGTH '-interface' INTERFACE -Windows 'netsh interface ipv6 add route' NETWORK ADDRESS/PREFIXLENGTH INTERFACE +Linux 'route add -A inet6' NETWORK_ADDRESS'/'PREFIXLENGTH INTERFACE +Linux iproute2 'ip route add' NETWORK_ADDRESS'/'PREFIXLENGTH 'dev' INTERFACE +FreeBSD 'route add -inet6' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS +OpenBSD 'route add -inet6' NETWORK_ADDRESS LOCAL_ADDRESS '-prefixlen' PREFIXLENGTH +NetBSD 'route add -inet6' NETWORK_ADDRESS LOCAL_ADDRESS '-prefixlen' PREFIXLENGTH +Solaris 'route add -inet6' NETWORK_ADDRESS'/'PREFIXLENGTH LOCAL_ADDRESS '-interface' +Darwin (MacOS/X) ? +Windows 'netsh interface ipv6 add route' NETWORK ADDRESS/PREFIXLENGTH INTERFACE  File: tinc.info, Node: Automatically starting tinc, Prev: Routes, Up: Platform specific information -7.3 Automatically starting tinc +9.3 Automatically starting tinc =============================== * Menu: @@ -2388,7 +3305,7 @@ File: tinc.info, Node: Automatically starting tinc, Prev: Routes, Up: Platfor  File: tinc.info, Node: Linux, Next: Windows, Up: Automatically starting tinc -7.3.1 Linux +9.3.1 Linux ----------- There are many Linux distributions, and historically, many of them had @@ -2405,35 +3322,35 @@ ensure it is started at boot time: systemctl enable tinc systemctl enable tinc@foo - To start the tinc daemon immediately if it wasn't already running, -use the following command: +To start the tinc daemon immediately if it wasn't already running, use +the following command: systemctl start tinc@foo - You can also use 'systemctl start tinc', this will start all tinc +You can also use 'systemctl start tinc', this will start all tinc daemons that are enabled. You can stop and disable tinc networks in the same way. - If your system is not using systemd, then you have to look up your +If your system is not using systemd, then you have to look up your distribution's way of starting tinc at boot time.  File: tinc.info, Node: Windows, Next: Other platforms, Prev: Linux, Up: Automatically starting tinc -7.3.2 Windows +9.3.2 Windows ------------- -On Windows, if tinc is started without the '-D' or '--no-detach' option, -it will automatically register itself as a service that is started at -boot time. When tinc is stopped using the '-k' or '--kill', it will -also automatically unregister itself. Once tinc is registered as a -service, it is also possible to stop and start tinc using the Windows -Services Manager. +On Windows, if tinc is started with the 'tinc start' command without +using the '-D' or '--no-detach' option, it will automatically register +itself as a service that is started at boot time. When tinc is stopped +using the 'tinc stop' command, it will also automatically unregister +itself. Once tinc is registered as a service, it is also possible to +stop and start tinc using the Windows Services Manager.  File: tinc.info, Node: Other platforms, Prev: Windows, Up: Automatically starting tinc -7.3.3 Other platforms +9.3.3 Other platforms --------------------- On platforms other than the ones mentioned in the earlier sections, you @@ -2442,8 +3359,8 @@ have to look up your platform's way of starting programs at boot time.  File: tinc.info, Node: About us, Next: Concept Index, Prev: Platform specific information, Up: Top -8 About us -********** +10 About us +*********** * Menu: @@ -2453,29 +3370,29 @@ File: tinc.info, Node: About us, Next: Concept Index, Prev: Platform specific  File: tinc.info, Node: Contact information, Next: Authors, Up: About us -8.1 Contact information -======================= +10.1 Contact information +======================== Tinc's website is at , this server is located in the Netherlands. - We have an IRC channel on the FreeNode and OFTC IRC networks. -Connect to irc.freenode.net (https://freenode.net/) or irc.oftc.net +We have an IRC channel on the FreeNode and OFTC IRC networks. Connect +to irc.freenode.net (https://freenode.net/) or irc.oftc.net (https://www.oftc.net/) and join channel #tinc.  File: tinc.info, Node: Authors, Prev: Contact information, Up: About us -8.2 Authors -=========== +10.2 Authors +============ Ivo Timmermans (zarq) Guus Sliepen (guus) () - We have received a lot of valuable input from users. With their -help, tinc has become the flexible and robust tool that it is today. We -have composed a list of contributions, in the file called 'THANKS' in -the source distribution. +We have received a lot of valuable input from users. With their help, +tinc has become the flexible and robust tool that it is today. We have +composed a list of contributions, in the file called 'THANKS' in the +source distribution.  File: tinc.info, Node: Concept Index, Prev: About us, Up: Top @@ -2486,8 +3403,9 @@ Concept Index [index] * Menu: -* ACK: Authentication protocol. - (line 10) +* ACK: Legacy authentication protocol. + (line 6) +* add: tinc commands. (line 22) * Address: Host configuration variables. (line 6) * AddressFamily: Main configuration variables. @@ -2495,214 +3413,296 @@ Concept Index * ADD_EDGE: The meta-protocol. (line 22) * ADD_SUBNET: The meta-protocol. (line 22) * ANS_KEY: The meta-protocol. (line 63) -* authentication: Authentication protocol. - (line 6) +* AutoConnect: Main configuration variables. + (line 12) +* batch: tinc runtime options. + (line 18) * binary package: Building and installing tinc. (line 9) * BindToAddress: Main configuration variables. - (line 12) + (line 16) * BindToInterface: Main configuration variables. - (line 25) + (line 23) * Broadcast: Main configuration variables. (line 33) +* BroadcastSubnet: Main configuration variables. + (line 53) * Cabal: Security. (line 6) -* CHALLENGE: Authentication protocol. - (line 10) -* CHAL_REPLY: Authentication protocol. - (line 10) +* CHALLENGE: Legacy authentication protocol. + (line 6) +* CHAL_REPLY: Legacy authentication protocol. + (line 6) * CIDR notation: Host configuration variables. - (line 93) + (line 101) * Cipher: Host configuration variables. (line 14) * ClampMSS: Host configuration variables. - (line 20) + (line 22) * client: How connections work. - (line 18) + (line 12) * command line: Runtime options. (line 9) +* command line interface: Controlling tinc. (line 6) * Compression: Host configuration variables. - (line 26) + (line 28) * connection: The connection. (line 6) * ConnectTo: Main configuration variables. - (line 53) + (line 65) * daemon: Running tinc. (line 11) * data-protocol: The meta-connection. (line 18) +* debug: tinc commands. (line 127) * debug level: Runtime options. (line 17) * debug levels: Debug levels. (line 6) * DecrementTTL: Main configuration variables. - (line 64) + (line 76) +* del: tinc commands. (line 27) * DEL_EDGE: The meta-protocol. (line 46) * DEL_SUBNET: The meta-protocol. (line 46) * Device: Main configuration variables. - (line 73) -* DEVICE: Scripts. (line 64) + (line 85) +* DEVICE: Scripts. (line 71) * device files: Device files. (line 6) +* DeviceStandby: Main configuration variables. + (line 92) * DeviceType: Main configuration variables. - (line 79) + (line 99) * Digest: Host configuration variables. - (line 31) + (line 33) * DirectOnly: Main configuration variables. - (line 149) + (line 177) +* disconnect: tinc commands. (line 142) * dummy: Main configuration variables. - (line 86) + (line 106) +* dump: tinc commands. (line 95) +* Ed25519PrivateKeyFile: Main configuration variables. + (line 184) +* edit: tinc commands. (line 32) * encapsulating: The UDP tunnel. (line 30) * encryption: Encryption of network packets. (line 6) -* environment variables: Scripts. (line 53) +* environment variables: Scripts. (line 59) * example: Example configuration. (line 6) +* exchange: tinc commands. (line 48) +* exchange-all: tinc commands. (line 51) * exec: Main configuration variables. - (line 319) + (line 376) +* ExperimentalProtocol: Main configuration variables. + (line 188) +* export: tinc commands. (line 36) +* export-all: tinc commands. (line 40) +* fd: Main configuration variables. + (line 129) * Forwarding: Main configuration variables. - (line 156) -* frame type: The UDP tunnel. (line 6) -* GraphDumpFile: Main configuration variables. - (line 176) -* Hostnames: Main configuration variables. - (line 184) -* http: Main configuration variables. - (line 316) -* hub: Main configuration variables. - (line 254) -* ID: Authentication protocol. - (line 10) -* IffOneQueue: Main configuration variables. (line 195) -* IndirectData: Host configuration variables. +* frame type: The UDP tunnel. (line 6) +* fsck: tinc commands. (line 160) +* FWMark: Main configuration variables. + (line 217) +* generate-ed25519-keys: tinc commands. (line 86) +* generate-keys: tinc commands. (line 81) +* generate-rsa-keys: tinc commands. (line 89) +* get: tinc commands. (line 11) +* graph: tinc commands. (line 108) +* Hostnames: Main configuration variables. + (line 223) +* http: Main configuration variables. + (line 373) +* hub: Main configuration variables. + (line 291) +* ID: Legacy authentication protocol. + (line 6) +* Ifconfig: Invitation file format. (line 36) +* import: tinc commands. (line 43) +* IndirectData: Host configuration variables. + (line 40) +* info: tinc commands. (line 120) +* init: tinc commands. (line 6) * Interface: Main configuration variables. - (line 198) -* INTERFACE: Scripts. (line 67) + (line 234) +* INTERFACE: Scripts. (line 74) +* InvitationExpire: Main configuration variables. + (line 296) +* INVITATION_FILE: Scripts. (line 97) +* INVITATION_URL: Scripts. (line 101) +* invite: tinc commands. (line 54) * IRC: Contact information. (line 9) -* key generation: Generating keypairs. (line 6) +* join: tinc commands. (line 59) * KeyExpire: Main configuration variables. - (line 206) + (line 299) * KEY_CHANGED: The meta-protocol. (line 63) +* legacy authentication protocol: Legacy authentication protocol. + (line 6) +* libcurses: libcurses. (line 6) * libraries: Libraries. (line 6) +* libreadline: libreadline. (line 6) * LibreSSL: LibreSSL/OpenSSL. (line 6) * license: LibreSSL/OpenSSL. (line 38) +* ListenAddress: Main configuration variables. + (line 242) * LocalDiscovery: Main configuration variables. - (line 212) -* lzo: lzo. (line 6) + (line 254) +* log: tinc commands. (line 130) +* LogLevel: Main configuration variables. + (line 265) +* LZO: LZO. (line 6) * MACExpire: Main configuration variables. - (line 223) + (line 305) * MACLength: Host configuration variables. - (line 44) -* MaxTimeout: Main configuration variables. - (line 228) + (line 45) +* MaxConnectionBurst: Main configuration variables. + (line 310) * meta-protocol: The meta-connection. (line 18) -* META_KEY: Authentication protocol. - (line 10) +* META_KEY: Legacy authentication protocol. + (line 6) * Mode: Main configuration variables. - (line 232) + (line 269) +* MTUInfoInterval: Host configuration variables. + (line 60) * multicast: Main configuration variables. - (line 98) + (line 118) * multiple networks: Multiple networks. (line 6) * Name: Main configuration variables. - (line 259) -* NAME: Scripts. (line 61) -* netmask: Network interfaces. (line 33) + (line 316) +* NAME: Scripts. (line 68) +* netmask: Network interfaces. (line 40) * netname: Multiple networks. (line 6) -* NETNAME: Scripts. (line 58) +* NETNAME: Scripts. (line 65) +* NETNAME <1>: tinc environment variables. + (line 6) +* network: tinc commands. (line 156) * Network Administrators Guide: Configuration introduction. (line 15) -* NODE: Scripts. (line 71) +* NODE: Scripts. (line 78) * OpenSSL: LibreSSL/OpenSSL. (line 6) * options: Runtime options. (line 9) +* pcap: tinc commands. (line 150) * PEM format: Host configuration variables. - (line 69) + (line 77) +* pid: tinc commands. (line 78) * PING: The meta-protocol. (line 88) * PingInterval: Main configuration variables. - (line 270) + (line 327) * PingTimeout: Main configuration variables. - (line 274) + (line 331) * platforms: Supported platforms. (line 6) * PMTU: Host configuration variables. - (line 49) -* PMTUDiscovery: Host configuration variables. (line 52) +* PMTUDiscovery: Host configuration variables. + (line 55) * PONG: The meta-protocol. (line 88) * Port: Host configuration variables. - (line 57) + (line 65) * port numbers: Other files. (line 17) * PriorityInheritance: Main configuration variables. - (line 280) + (line 337) * private: Virtual Private Networks. (line 10) * PrivateKey: Main configuration variables. - (line 285) + (line 342) * PrivateKeyFile: Main configuration variables. - (line 291) + (line 348) * ProcessPriority: Main configuration variables. - (line 296) + (line 353) * Proxy: Main configuration variables. - (line 301) + (line 358) * PublicKey: Host configuration variables. - (line 61) + (line 69) * PublicKeyFile: Host configuration variables. - (line 64) + (line 72) +* purge: tinc commands. (line 124) * raw_socket: Main configuration variables. - (line 91) -* release: Supported platforms. (line 14) -* REMOTEADDRESS: Scripts. (line 76) -* REMOTEPORT: Scripts. (line 79) + (line 111) +* release: Supported platforms. (line 13) +* reload: tinc commands. (line 73) +* REMOTEADDRESS: Scripts. (line 83) +* REMOTEPORT: Scripts. (line 86) * ReplayWindow: Main configuration variables. - (line 324) + (line 381) * requirements: Libraries. (line 6) * REQ_KEY: The meta-protocol. (line 63) +* restart: tinc commands. (line 70) +* retry: tinc commands. (line 135) +* Route: Invitation file format. + (line 52) * router: Main configuration variables. - (line 235) + (line 272) * runtime options: Runtime options. (line 9) * scalability: tinc. (line 19) * scripts: Scripts. (line 6) * server: How connections work. - (line 18) + (line 12) +* set: tinc commands. (line 16) +* shell: Controlling tinc. (line 11) +* sign: tinc commands. (line 172) * signals: Signals. (line 6) * socks4: Main configuration variables. - (line 305) + (line 362) * socks5: Main configuration variables. - (line 310) + (line 367) +* SPTPS: Simple Peer-to-Peer Security. + (line 6) +* start: tinc commands. (line 64) +* stop: tinc commands. (line 67) * StrictSubnets: Main configuration variables. - (line 335) + (line 392) * Subnet: Host configuration variables. - (line 76) -* SUBNET: Scripts. (line 83) -* Subnet weight: Host configuration variables. - (line 98) + (line 84) +* SUBNET: Scripts. (line 90) * SVPN: Security. (line 11) * switch: Main configuration variables. - (line 243) + (line 280) * systemd: Linux. (line 6) * TCP: The meta-connection. (line 10) * TCPonly: Host configuration variables. - (line 105) + (line 113) * tinc: Introduction. (line 6) * TINC: Security. (line 6) * tinc-down: Scripts. (line 29) * tinc-up: Scripts. (line 19) * tinc-up <1>: Network interfaces. (line 19) * tincd: tinc. (line 14) +* top: tinc commands. (line 145) +* top <1>: tinc top. (line 6) * traditional VPNs: tinc. (line 19) * tunifhead: Main configuration variables. - (line 133) + (line 161) * TunnelServer: Main configuration variables. - (line 342) + (line 399) * tunnohead: Main configuration variables. - (line 127) + (line 155) * UDP: The UDP tunnel. (line 30) * UDP <1>: Encryption of network packets. - (line 12) + (line 11) +* UDPDiscoveryInterval: Main configuration variables. + (line 419) +* UDPDiscoveryKeepaliveInterval: Main configuration variables. + (line 413) +* UDPDiscoveryTimeout: Main configuration variables. + (line 423) +* UDPDiscovey: Main configuration variables. + (line 406) +* UDPInfoInterval: Main configuration variables. + (line 428) * UDPRcvBuf: Main configuration variables. - (line 349) + (line 432) * UDPSndBuf: Main configuration variables. - (line 354) + (line 438) * UML: Main configuration variables. - (line 109) + (line 136) * Universal tun/tap: Configuration of Linux kernels. (line 6) +* UPnP: Main configuration variables. + (line 444) +* UPnPDiscoverWait: Main configuration variables. + (line 455) +* UPnPRefreshPeriod: Main configuration variables. + (line 459) * utun: Main configuration variables. - (line 140) + (line 168) * VDE: Main configuration variables. - (line 114) + (line 142) +* verify: tinc commands. (line 177) * virtual: Virtual Private Networks. (line 18) * virtual network device: The UDP tunnel. (line 6) @@ -2710,77 +3710,90 @@ Concept Index (line 6) * vpnd: tinc. (line 6) * website: Contact information. (line 6) -* WEIGHT: Scripts. (line 86) +* Weight: Host configuration variables. + (line 120) +* WEIGHT: Scripts. (line 93) * zlib: zlib. (line 6)  Tag Table: -Node: Top806 -Node: Introduction1105 -Node: Virtual Private Networks1915 -Node: tinc3639 -Node: Supported platforms5167 -Node: Preparations5868 -Node: Configuring the kernel6124 -Node: Configuration of Linux kernels6534 -Node: Configuration of FreeBSD kernels7389 -Node: Configuration of OpenBSD kernels7854 -Node: Configuration of NetBSD kernels8211 -Node: Configuration of Solaris kernels8616 -Node: Configuration of Darwin (Mac OS X) kernels9279 -Node: Configuration of Windows10098 -Node: Libraries10638 -Node: LibreSSL/OpenSSL11047 -Node: zlib13589 -Node: lzo14618 -Node: Installation15601 -Node: Building and installing tinc16511 -Node: Darwin (Mac OS X) build environment17171 -Node: Cygwin (Windows) build environment17736 -Node: MinGW (Windows) build environment18325 -Node: System files18919 -Node: Device files19184 -Node: Other files19600 -Node: Configuration20213 -Node: Configuration introduction20524 -Node: Multiple networks21792 -Node: How connections work23218 -Node: Configuration files24440 -Node: Main configuration variables25934 -Node: Host configuration variables42182 -Node: Scripts47714 -Node: How to configure50980 -Node: Generating keypairs52238 -Node: Network interfaces52737 -Node: Example configuration54585 -Node: Running tinc59910 -Node: Runtime options60500 -Node: Signals64125 -Node: Debug levels65316 -Node: Solving problems66252 -Node: Error messages67804 -Node: Sending bug reports71813 -Node: Technical information72760 -Node: The connection72991 -Node: The UDP tunnel73303 -Node: The meta-connection76355 -Node: The meta-protocol77824 -Node: Security82841 -Node: Authentication protocol83983 -Node: Encryption of network packets89028 -Node: Security issues90404 -Node: Platform specific information92044 -Node: Interface configuration92304 -Node: Routes94600 -Node: Automatically starting tinc96650 -Node: Linux96873 -Node: Windows98094 -Node: Other platforms98599 -Node: About us98881 -Node: Contact information99056 -Node: Authors99459 -Node: Concept Index99864 +Node: Top821 +Node: Introduction1157 +Node: Virtual Private Networks1961 +Node: tinc3673 +Node: Supported platforms5186 +Node: Preparations5839 +Node: Configuring the kernel6095 +Node: Configuration of Linux kernels6504 +Node: Configuration of FreeBSD kernels7353 +Node: Configuration of OpenBSD kernels7818 +Node: Configuration of NetBSD kernels8175 +Node: Configuration of Solaris kernels8577 +Node: Configuration of Darwin (MacOS/X) kernels9239 +Node: Configuration of Windows10052 +Node: Libraries10591 +Node: LibreSSL/OpenSSL11048 +Node: zlib13576 +Node: LZO14597 +Node: libcurses15590 +Node: libreadline16503 +Node: Installation17443 +Node: Building and installing tinc18347 +Node: Darwin (MacOS/X) build environment18964 +Node: MinGW (Windows) build environment19522 +Node: System files20110 +Node: Device files20375 +Node: Other files20788 +Node: Configuration21399 +Node: Configuration introduction21686 +Node: Multiple networks23209 +Node: How connections work24627 +Node: Configuration files26891 +Node: Main configuration variables28562 +Node: Host configuration variables50200 +Node: Scripts56272 +Node: How to configure60259 +Node: Network interfaces64190 +Node: Example configuration66590 +Node: Running tinc71682 +Node: Runtime options72269 +Node: Signals75596 +Node: Debug levels76445 +Node: Solving problems77381 +Node: Error messages78807 +Node: Sending bug reports83127 +Node: Controlling tinc84074 +Node: tinc runtime options84810 +Node: tinc environment variables85646 +Node: tinc commands85975 +Node: tinc examples92837 +Node: tinc top93397 +Node: Invitations94981 +Node: How invitations work95644 +Node: Invitation file format97937 +Node: Writing an invitation-created script100950 +Node: Technical information102013 +Node: The connection102243 +Node: The UDP tunnel102555 +Node: The meta-connection105591 +Node: The meta-protocol107049 +Node: Security112032 +Node: Legacy authentication protocol113369 +Node: Simple Peer-to-Peer Security117986 +Node: Encryption of network packets123631 +Node: Security issues126269 +Node: Platform specific information128861 +Node: Interface configuration129121 +Node: Routes131391 +Node: Automatically starting tinc133338 +Node: Linux133561 +Node: Windows134773 +Node: Other platforms135317 +Node: About us135599 +Node: Contact information135776 +Node: Authors136179 +Node: Concept Index136583  End Tag Table diff --git a/doc/tinc.texi b/doc/tinc.texi index 0420e6f..0ff95e9 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -15,7 +15,7 @@ This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon. -Copyright @copyright{} 1998-2019 Ivo Timmermans, +Copyright @copyright{} 1998-2021 Ivo Timmermans, Guus Sliepen and Wessel Dankers . @@ -30,6 +30,10 @@ permission notice identical to this one. @end ifinfo +@afourpaper +@paragraphindent none +@finalout + @titlepage @title tinc Manual @subtitle Setting up a Virtual Private Network with tinc @@ -39,7 +43,7 @@ permission notice identical to this one. @vskip 0pt plus 1filll This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon. -Copyright @copyright{} 1998-2019 Ivo Timmermans, +Copyright @copyright{} 1998-2021 Ivo Timmermans, Guus Sliepen and Wessel Dankers . @@ -65,6 +69,8 @@ permission notice identical to this one. * Installation:: * Configuration:: * Running tinc:: +* Controlling tinc:: +* Invitations:: * Technical information:: * Platform specific information:: * About us:: @@ -176,7 +182,7 @@ available too. @section Supported platforms @cindex platforms -Tinc has been verified to work under Linux, FreeBSD, OpenBSD, NetBSD, Mac OS X (Darwin), Solaris, and Windows (both natively and in a Cygwin environment), +Tinc has been verified to work under Linux, FreeBSD, OpenBSD, NetBSD, MacOS/X (Darwin), Solaris, and Windows, with various hardware architectures. These are some of the platforms that are supported by the universal tun/tap device driver or other virtual network device drivers. Without such a driver, tinc will most @@ -224,7 +230,7 @@ support tinc. * Configuration of OpenBSD kernels:: * Configuration of NetBSD kernels:: * Configuration of Solaris kernels:: -* Configuration of Darwin (Mac OS X) kernels:: +* Configuration of Darwin (MacOS/X) kernels:: * Configuration of Windows:: @end menu @@ -261,7 +267,7 @@ alias char-major-10-200 tun @subsection Configuration of FreeBSD kernels For FreeBSD version 4.1 and higher, tun and tap drivers are included in the default kernel configuration. -The tap driver can be loaded with @code{kldload if_tap}, or by adding @code{if_tap_load="YES"} to @file{/boot/loader.conf}. +The tap driver can be loaded with @command{kldload if_tap}, or by adding @samp{if_tap_load="YES"} to @file{/boot/loader.conf}. @c ================================================================== @@ -293,16 +299,16 @@ If the @file{net/if_tun.h} header file is missing, install it from the source pa @c ================================================================== -@node Configuration of Darwin (Mac OS X) kernels -@subsection Configuration of Darwin (Mac OS X) kernels +@node Configuration of Darwin (MacOS/X) kernels +@subsection Configuration of Darwin (MacOS/X) kernels Tinc on Darwin relies on a tunnel driver for its data acquisition from the kernel. OS X version 10.6.8 and later have a built-in tun driver called "utun". Tinc also supports the driver from @uref{http://tuntaposx.sourceforge.net/}, -which supports both tun and tap style devices. +which supports both tun and tap style devices, By default, tinc expects the tuntaposx driver to be installed. -To use the utun driver, set add @code{Device = utunX} to @file{tinc.conf}, +To use the utun driver, set add @samp{Device = utunX} to @file{tinc.conf}, where X is the desired number for the utun interface. You can also omit the number, in which case the first free number will be chosen. @@ -324,14 +330,17 @@ as explained in the rest of the documentation. @cindex requirements @cindex libraries -Before you can configure or build tinc, you need to have the LibreSSL or OpenSSL, -zlib and lzo libraries installed on your system. If you try to configure tinc without -having them installed, configure will give you an error message, and stop. +Before you can configure or build tinc, you need to have the LibreSSL or OpenSSL, zlib, +LZO, curses and readline libraries installed on your system. If you try to +configure tinc without having them installed, configure will give you an error +message, and stop. @menu * LibreSSL/OpenSSL:: * zlib:: -* lzo:: +* LZO:: +* libcurses:: +* libreadline:: @end menu @@ -354,7 +363,7 @@ of this package. If your operating system comes neither with LibreSSL or OpenSSL, you have to install one manually. It is recommended that you get the latest version of -LibreSSL from @url{http://www.libressl.org/}. Instructions on how to +LibreSSL from @url{https://www.libressl.org/}. Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default). @@ -412,7 +421,7 @@ by the zlib library. If this library is not installed, you will get an error when running the configure script. You can either install the zlib library, or disable support -for zlib compression by using the "--disable-zlib" option when running the +for zlib compression by using the @option{--disable-zlib} option when running the configure script. Note that if you disable support for zlib, the resulting binary will not work correctly on VPNs where zlib compression is used. @@ -428,15 +437,15 @@ default). @c ================================================================== -@node lzo -@subsection lzo +@node LZO +@subsection LZO -@cindex lzo +@cindex LZO Another form of compression is offered using the LZO library. If this library is not installed, you will get an error when running the configure script. You can either install the LZO library, or disable support -for LZO compression by using the "--disable-lzo" option when running the +for LZO compression by using the @option{--disable-lzo} option when running the configure script. Note that if you disable support for LZO, the resulting binary will not work correctly on VPNs where LZO compression is used. @@ -444,13 +453,58 @@ You can use your operating system's package manager to install this if available. Make sure you install the development AND runtime versions of this package. -If you have to install lzo manually, you can get the source code +If you have to install LZO manually, you can get the source code from @url{https://www.oberhumer.com/opensource/lzo/}. Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default). +@c ================================================================== +@node libcurses +@subsection libcurses + +@cindex libcurses +For the @command{tinc top} command, tinc requires a curses library. + +If this library is not installed, you will get an error when running the +configure script. You can either install a suitable curses library, or disable +all functionality that depends on a curses library by using the +@option{--disable-curses} option when running the configure script. + +There are several curses libraries. It is recommended that you install +"ncurses" (@url{https://invisible-island.net/ncurses/}), +however other curses libraries should also work. +In particular, "PDCurses" (@url{https://pdcurses.sourceforge.io/}) +is recommended if you want to compile tinc for Windows. + +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. + + +@c ================================================================== +@node libreadline +@subsection libreadline + +@cindex libreadline +For the @command{tinc} command's shell functionality, tinc uses the readline library. + +If this library is not installed, you will get an error when running the +configure script. You can either install a suitable readline library, or +disable all functionality that depends on a readline library by using the +@option{--disable-readline} option when running the configure script. + +You can use your operating system's package manager to install this if +available. Make sure you install the development AND runtime versions +of this package. + +If you have to install libreadline manually, you can get the source code from +@url{https://www.gnu.org/software/readline/}. Instructions on how to configure, +build and install this package are included within the package. Please make +sure you build development and runtime libraries (which is the default). + + @c @c @c @@ -498,15 +552,14 @@ you can use the package management tools of that distribution to install tinc. The documentation that comes along with your distribution will tell you how to do that. @menu -* Darwin (Mac OS X) build environment:: -* Cygwin (Windows) build environment:: +* Darwin (MacOS/X) build environment:: * MinGW (Windows) build environment:: @end menu @c ================================================================== -@node Darwin (Mac OS X) build environment -@subsection Darwin (Mac OS X) build environment +@node Darwin (MacOS/X) build environment +@subsection Darwin (MacOS/X) build environment In order to build tinc on Darwin, you need to install Xcode from @uref{https://developer.apple.com/xcode/}. It might also help to install a recent version of Fink from @uref{http://www.finkproject.org/}. @@ -514,17 +567,6 @@ It might also help to install a recent version of Fink from @uref{http://www.fin You need to download and install LibreSSL (or OpenSSL) and LZO, either directly from their websites (see @ref{Libraries}) or using Fink. -@c ================================================================== -@node Cygwin (Windows) build environment -@subsection Cygwin (Windows) build environment - -If Cygwin hasn't already been installed, install it directly from -@uref{https://www.cygwin.com/}. - -When tinc is compiled in a Cygwin environment, it can only be run in this environment, -but all programs, including those started outside the Cygwin environment, will be able to use the VPN. -It will also support all features. - @c ================================================================== @node MinGW (Windows) build environment @subsection MinGW (Windows) build environment @@ -585,7 +627,7 @@ myvpn 10.0.0.0 @cindex port numbers You may add this line to @file{/etc/services}. The effect is that you -may supply a @samp{tinc} as a valid port number to some programs. The +may supply @samp{tinc} as a valid port number to some programs. The number 655 is registered with the IANA. @example @@ -615,7 +657,6 @@ tinc 655/udp TINC * Multiple networks:: * How connections work:: * Configuration files:: -* Generating keypairs:: * Network interfaces:: * Example configuration:: @end menu @@ -638,13 +679,19 @@ you will not find the answers in this documentation. Make sure you have an adequate understanding of networks in general. @cindex Network Administrators Guide A good resource on networking is the -@uref{http://www.tldp.org/LDP/nag2/, Linux Network Administrators Guide}. +@uref{https://www.tldp.org/LDP/nag2/, Linux Network Administrators Guide}. If you have everything clearly pictured in your mind, proceed in the following order: -First, generate the configuration files (@file{tinc.conf}, your host configuration file, @file{tinc-up} and perhaps @file{tinc-down}). -Then generate the keypairs. -Finally, distribute the host configuration files. +First, create the initial configuration files and public/private key pairs using the following command: +@example +tinc -n @var{NETNAME} init @var{NAME} +@end example +Second, use @command{tinc -n @var{NETNAME} add ...} to further configure tinc. +Finally, export your host configuration file using @command{tinc -n @var{NETNAME} export} and send it to those +people or computers you want tinc to connect to. +They should send you their host configuration file back, which you can import using @command{tinc -n @var{NETNAME} import}. + These steps are described in the subsections below. @@ -654,30 +701,29 @@ These steps are described in the subsections below. @cindex multiple networks @cindex netname + In order to allow you to run more than one tinc daemon on one computer, for instance if your computer is part of more than one VPN, you can assign a @var{netname} to your VPN. It is not required if you only run one tinc daemon, -it doesn't even have to be the same on all the sites of your VPN, +it doesn't even have to be the same on all the nodes of your VPN, but it is recommended that you choose one anyway. We will assume you use a netname throughout this document. -This means that you call tincd with the -n argument, -which will assign a netname to this daemon. +This means that you call tinc with the -n argument, +which will specify the netname. -The effect of this is that the daemon will set its configuration -root to @file{@value{sysconfdir}/tinc/@var{netname}/}, where @var{netname} is your argument to the -n -option. You'll notice that it appears in syslog as @file{tinc.@var{netname}}. +The effect of this option is that tinc will set its configuration +root to @file{@value{sysconfdir}/tinc/@var{netname}/}, where @var{netname} is your argument to the -n option. +You will also notice that log messages it appears in syslog as coming from @file{tinc.@var{netname}}, +and on Linux, unless specified otherwise, the name of the virtual network interface will be the same as the network name. However, it is not strictly necessary that you call tinc with the -n -option. In this case, the network name would just be empty, and it will -be used as such. tinc now looks for files in @file{@value{sysconfdir}/tinc/}, instead of -@file{@value{sysconfdir}/tinc/@var{netname}/}; the configuration file should be @file{@value{sysconfdir}/tinc/tinc.conf}, -and the host configuration files are now expected to be in @file{@value{sysconfdir}/tinc/hosts/}. - -But it is highly recommended that you use this feature of tinc, because -it will be so much clearer whom your daemon talks to. Hence, we will -assume that you use it. +option. If you do not use it, the network name will just be empty, and +tinc will look for files in @file{@value{sysconfdir}/tinc/} instead of +@file{@value{sysconfdir}/tinc/@var{netname}/}; +the configuration file will then be @file{@value{sysconfdir}/tinc/tinc.conf}, +and the host configuration files are expected to be in @file{@value{sysconfdir}/tinc/hosts/}. @c ================================================================== @@ -686,24 +732,36 @@ assume that you use it. When tinc starts up, it parses the command-line options and then reads in the configuration file tinc.conf. -If it sees one or more `ConnectTo' values pointing to other tinc daemons in that file, -it will try to connect to those other daemons. -Whether this succeeds or not and whether `ConnectTo' is specified or not, -tinc will listen for incoming connection from other daemons. -If you did specify a `ConnectTo' value and the other side is not responding, -tinc will keep retrying. -This means that once started, tinc will stay running until you tell it to stop, -and failures to connect to other tinc daemons will not stop your tinc daemon -for trying again later. -This means you don't have to intervene if there are temporary network problems. +It will then start listening for incoming connection from other daemons, +and will by default also automatically try to connect to known peers. +By default, tinc will try to keep at least 3 working meta-connections alive at all times. @cindex client @cindex server There is no real distinction between a server and a client in tinc. -If you wish, you can view a tinc daemon without a `ConnectTo' value as a server, -and one which does specify such a value as a client. +If you wish, you can view a tinc daemon without a `ConnectTo' statement in tinc.conf and `AutoConnect = no' as a server, +and one which does have one or more `ConnectTo' statements or `Autoconnect = yes' (which is the default) as a client. It does not matter if two tinc daemons have a `ConnectTo' value pointing to each other however. +Connections specified using `ConnectTo' are so-called meta-connections. +Tinc daemons exchange information about all other daemon they know about via these meta-connections. +After learning about all the daemons in the VPN, +tinc will create other connections as necessary in order to communicate with them. +For example, if there are three daemons named A, B and C, and A has @samp{ConnectTo = B} in its tinc.conf file, +and C has @samp{ConnectTo = B} in its tinc.conf file, then A will learn about C from B, +and will be able to exchange VPN packets with C without the need to have @samp{ConnectTo = C} in its tinc.conf file. + +It could be that some daemons are located behind a Network Address Translation (NAT) device, or behind a firewall. +In the above scenario with three daemons, if A and C are behind a NAT, +B will automatically help A and C punch holes through their NAT, +in a way similar to the STUN protocol, so that A and C can still communicate with each other directly. +It is not always possible to do this however, and firewalls might also prevent direct communication. +In that case, VPN packets between A and C will be forwarded by B. + +In effect, all nodes in the VPN will be able to talk to each other, as long as +there is a path of meta-connections between them, and whenever possible, two +nodes will communicate with each other directly. + @c ================================================================== @node Configuration files @@ -735,7 +793,10 @@ listed in this document can also be put in put host specific configuration options in the host configuration file, as this makes it easy to exchange with other nodes. -In this section all valid variables are listed in alphabetical order. +You can edit the config file manually, but it is recommended that you use +the tinc command to change configuration variables for you. + +In the following two subsections all valid variables are listed in alphabetical order. The default value is given between parentheses, other comments are between square brackets. @@ -758,18 +819,17 @@ This option affects the address family of listening and outgoing sockets. If any is selected, then depending on the operating system both IPv4 and IPv6 or just IPv6 listening sockets will be created. +@cindex AutoConnect +@item AutoConnect = (yes) +If set to yes, tinc will automatically set up meta connections to other nodes, +without requiring @var{ConnectTo} variables. + @cindex BindToAddress -@item BindToAddress = <@var{address}> [<@var{port}>] [experimental] -If your computer has more than one IPv4 or IPv6 address, tinc -will by default listen on all of them for incoming connections. -Multiple BindToAddress variables may be specified, -in which case listening sockets for each specified address are made. - -If no @var{port} is specified, the socket will be bound to the port specified by the Port option, -or to port 655 if neither is given. -To only bind to a specific port but not to a specific address, use "*" for the @var{address}. - -This option may not work on all platforms. +@item BindToAddress = <@var{address}> [<@var{port}>] +This is the same as ListenAddress, however the address given with the BindToAddress option +will also be used for outgoing connections. +This is useful if your computer has more than one IPv4 or IPv6 address, +and you want tinc to only use a specific one for outgoing packets. @cindex BindToInterface @item BindToInterface = <@var{interface}> [experimental] @@ -779,6 +839,8 @@ possible to bind tinc to a single interface like eth0 or ppp0 with this variable. This option may not work on all platforms. +Also, on some platforms it will not actually bind to an interface, +but rather to the address that the interface has at the moment a socket is created. @cindex Broadcast @item Broadcast = (mst) [experimental] @@ -799,6 +861,18 @@ Broadcast packets received from other nodes are never forwarded. If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. @end table +@cindex BroadcastSubnet +@item BroadcastSubnet = @var{address}[/@var{prefixlength}] +Declares a broadcast subnet. +Any packet with a destination address falling into such a subnet will be routed as a broadcast +(provided all nodes have it declared). +This is most useful to declare subnet broadcast addresses (e.g. 10.42.255.255), +otherwise tinc won't know what to do with them. + +Note that global broadcast addresses (MAC ff:ff:ff:ff:ff:ff, IPv4 255.255.255.255), +as well as multicast space (IPv4 224.0.0.0/4, IPv6 ff00::/8) +are always considered broadcast addresses and don't need to be declared. + @cindex ConnectTo @item ConnectTo = <@var{name}> Specifies which other tinc daemon to connect to on startup. @@ -807,7 +881,7 @@ in which case outgoing connections to each specified tinc daemon are made. The names should be known to this tinc daemon (i.e., there should be a host configuration file for the name on the ConnectTo line). -If you don't specify a host with ConnectTo, +If you don't specify a host with ConnectTo and have disabled AutoConnect, tinc won't try to connect to other daemons at all, and will instead just listen for incoming connections. @@ -824,10 +898,18 @@ Do not use this option if you use switch mode and want to use IPv6. @item Device = <@var{device}> (@file{/dev/tap0}, @file{/dev/net/tun} or other depending on platform) The virtual network device to use. Tinc will automatically detect what kind of device it is. +Note that you can only use one device per daemon. Under Windows, use @var{Interface} instead of @var{Device}. Note that you can only use one device per daemon. See also @ref{Device files}. +@cindex DeviceStandby +@item DeviceStandby = (no) +When disabled, tinc calls @file{tinc-up} on startup, and @file{tinc-down} on shutdown. +When enabled, tinc will only call @file{tinc-up} when at least one node is reachable, +and will call @file{tinc-down} as soon as no nodes are reachable. +On Windows, this also determines when the virtual network interface "cable" is "plugged". + @cindex DeviceType @item DeviceType = <@var{type}> (platform dependent) The type of the virtual network device. @@ -857,6 +939,13 @@ This can be used to connect to UML, QEMU or KVM instances listening on the same Do NOT connect multiple tinc daemons to the same multicast address, this will very likely cause routing loops. Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. +@cindex fd +@item fd +Use a file descriptor, given directly as an integer or passed through a unix domain socket. +On Linux, an abstract socket address can be specified by using @samp{@@} as a prefix. +All packets are read from this interface. +Packets received for the local node are written to it. + @cindex UML @item uml (not compiled in by default) Create a UNIX socket with the filename specified by @@ -913,6 +1002,19 @@ but which would have to be forwarded by an intermediate node, are dropped instea When combined with the IndirectData option, packets for nodes for which we do not have a meta connection with are also dropped. +@cindex Ed25519PrivateKeyFile +@item Ed25519PrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/ed25519_key.priv}) +The file in which the private Ed25519 key of this tinc daemon resides. +This is only used if ExperimentalProtocol is enabled. + +@cindex ExperimentalProtocol +@item ExperimentalProtocol = (yes) +When this option is enabled, the SPTPS protocol will be used when connecting to nodes that also support it. +Ephemeral ECDH will be used for key exchanges, +and Ed25519 will be used instead of RSA for authentication. +When enabled, an Ed25519 key must have been generated before with +@command{tinc generate-ed25519-keys}. + @cindex Forwarding @item Forwarding = (internal) [experimental] This option selects the way indirect packets are forwarded. @@ -928,20 +1030,18 @@ Incoming packets that are meant for another node are forwarded by tinc internall This is the default mode, and unless you really know you need another forwarding mode, don't change it. @item kernel -Incoming packets are always sent to the TUN/TAP device, even if the packets are not for the local node. +Incoming packets using the legacy protocol are always sent to the TUN/TAP device, +even if the packets are not for the local node. This is less efficient, but allows the kernel to apply its routing and firewall rules on them, and can also help debugging. +Incoming packets using the SPTPS protocol are dropped, since they are end-to-end encrypted. @end table -@cindex GraphDumpFile -@item GraphDumpFile = <@var{filename}> [experimental] -If this option is present, -tinc will dump the current network graph to the file @var{filename} -every minute, unless there were no changes to the graph. -The file is in a format that can be read by graphviz tools. -If @var{filename} starts with a pipe symbol |, -then the rest of the filename is interpreted as a shell command -that is executed, the graph is then sent to stdin. +@cindex FWMark +@item FWMark = <@var{value}> (0) [experimental] +When set to a non-zero value, all TCP and UDP sockets created by tinc will use the given value as the firewall mark. +This can be used for mark-based routing or for packet filtering. +This option is currently only supported on Linux. @cindex Hostnames @item Hostnames = (no) @@ -953,10 +1053,6 @@ it does a lookup if your DNS server is not responding. This does not affect resolving hostnames to IP addresses from the configuration file, but whether hostnames should be resolved while logging. -@cindex IffOneQueue -@item IffOneQueue = (no) [experimental] -(Linux only) Set IFF_ONE_QUEUE flag on TUN/TAP devices. - @cindex Interface @item Interface = <@var{interface}> Defines the name of the interface corresponding to the virtual network device. @@ -964,31 +1060,32 @@ Depending on the operating system and the type of device this may or may not act Under Windows, this variable is used to select which network interface will be used. If you specified a Device, this variable is almost always already correctly set. -@cindex KeyExpire -@item KeyExpire = <@var{seconds}> (3600) -This option controls the time the encryption keys used to encrypt the data -are valid. It is common practice to change keys at regular intervals to -make it even harder for crackers, even though it is thought to be nearly -impossible to crack a single key. +@cindex ListenAddress +@item ListenAddress = <@var{address}> [<@var{port}>] +If your computer has more than one IPv4 or IPv6 address, tinc +will by default listen on all of them for incoming connections. +This option can be used to restrict which addresses tinc listens on. +Multiple ListenAddress variables may be specified, +in which case listening sockets for each specified address are made. + +If no @var{port} is specified, the socket will listen on the port specified by the Port option, +or to port 655 if neither is given. +To only listen on a specific port but not to a specific address, use @samp{*} for the @var{address}. @cindex LocalDiscovery -@item LocalDiscovery = (no) [experimental] +@item LocalDiscovery = (no) When enabled, tinc will try to detect peers that are on the same local network. This will allow direct communication using LAN addresses, even if both peers are behind a NAT and they only ConnectTo a third node outside the NAT, which normally would prevent the peers from learning each other's LAN address. -Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery. -This feature may not work in all possible situations. +Currently, local discovery is implemented by sending some packets to the local address of the node during UDP discovery. +This will not work with old nodes that don't transmit their local address. -@cindex MACExpire -@item MACExpire = <@var{seconds}> (600) -This option controls the amount of time MAC addresses are kept before they are removed. -This only has effect when Mode is set to "switch". - -@cindex MaxTimeout -@item MaxTimeout = <@var{seconds}> (900) -This is the maximum delay before trying to reconnect to other tinc daemons. +@cindex LogLevel +@item LogLevel = <@var{level}> (0) +This option controls the verbosity of the logging. +See @ref{Debug levels}. @cindex Mode @item Mode = (router) @@ -999,7 +1096,7 @@ This option selects the way packets are routed to other daemons. @item router In this mode Subnet variables in the host configuration files will be used to form a routing table. -Only unicast packets of routable protocols (IPv4 and IPv6) are supported in this mode. +Only packets of routable protocols (IPv4 and IPv6) are supported in this mode. This is the default mode, and unless you really know you need another mode, don't change it. @@ -1019,10 +1116,33 @@ every packet will be broadcast to the other daemons while no routing table is managed. @end table +@cindex InvitationExpire +@item InvitationExpire = <@var{seconds}> (604800) +This option controls the time invitations are valid. + +@cindex KeyExpire +@item KeyExpire = <@var{seconds}> (3600) +This option controls the time the encryption keys used to encrypt the data +are valid. It is common practice to change keys at regular intervals to +make it even harder for crackers, even though it is thought to be nearly +impossible to crack a single key. + +@cindex MACExpire +@item MACExpire = <@var{seconds}> (600) +This option controls the amount of time MAC addresses are kept before they are removed. +This only has effect when Mode is set to @samp{switch}. + +@cindex MaxConnectionBurst +@item MaxConnectionBurst = <@var{count}> (100) +This option controls how many connections tinc accepts in quick succession. +If there are more connections than the given number in a short time interval, +tinc will reduce the number of accepted connections to only one per second, +until the burst has passed. + @cindex Name @item Name = <@var{name}> [required] This is a symbolic name for this connection. -The name must consist only of alphanumeric and underscore characters (a-z, A-Z, 0-9 and _). +The name must consist only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and _), and is case sensitive. If Name starts with a $, then the contents of the environment variable that follows will be used. In that case, invalid characters will be converted to underscores. @@ -1054,7 +1174,7 @@ accidental eavesdropping if you are editing the configuration file. @cindex PrivateKeyFile @item PrivateKeyFile = <@var{path}> (@file{@value{sysconfdir}/tinc/@var{netname}/rsa_key.priv}) This is the full path name of the RSA private key file that was -generated by @samp{tincd --generate-keys}. It must be a full path, not a +generated by @command{tinc generate-keys}. It must be a full path, not a relative directory. @cindex ProcessPriority @@ -1090,10 +1210,10 @@ The environment variables @env{NAME}, @env{NODE}, @env{REMOTEADDRES} and @env{RE @end table @cindex ReplayWindow -@item ReplayWindow = (16) +@item ReplayWindow = (32) This is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet per bit, so for example -the default setting of 16 will track up to 128 packets in the window. In high +the default setting of 32 will track up to 256 packets in the window. In high bandwidth scenarios, setting this to a higher value can reduce packet loss from the interaction of replay tracking with underlying real packet loss and/or reordering. Setting this to zero will disable replay tracking completely and @@ -1115,15 +1235,60 @@ and will only allow connections with nodes for which host config files are prese @file{@value{sysconfdir}/tinc/@var{netname}/hosts/} directory. Setting this options also implicitly sets StrictSubnets. +@cindex UDPDiscovey +@item UDPDiscovery = (yes) +When this option is enabled tinc will try to establish UDP connectivity to nodes, +using TCP while it determines if a node is reachable over UDP. If it is disabled, +tinc always assumes a node is reachable over UDP. +Note that tinc will never use UDP with nodes that have TCPOnly enabled. + +@cindex UDPDiscoveryKeepaliveInterval +@item UDPDiscoveryKeepaliveInterval = (9) +The minimum amount of time between sending UDP ping datagrams to check UDP connectivity once it has been established. +Note that these pings are large, since they are used to verify link MTU as well. + +@cindex UDPDiscoveryInterval +@item UDPDiscoveryInterval = (2) +The minimum amount of time between sending UDP ping datagrams to try to establish UDP connectivity. + +@cindex UDPDiscoveryTimeout +@item UDPDiscoveryTimeout = (30) +If tinc doesn't receive any UDP ping replies over the specified interval, +it will assume UDP communication is broken and will fall back to TCP. + +@cindex UDPInfoInterval +@item UDPInfoInterval = (5) +The minimum amount of time between sending periodic updates about UDP addresses, which are mostly useful for UDP hole punching. + @cindex UDPRcvBuf -@item UDPRcvBuf = (OS default) +@item UDPRcvBuf = (1048576) Sets the socket receive buffer size for the UDP socket, in bytes. -If unset, the default buffer size will be used by the operating system. +If set to zero, the default buffer size will be used by the operating system. +Note: this setting can have a significant impact on performance, especially raw throughput. @cindex UDPSndBuf -@item UDPSndBuf = Pq OS default +@item UDPSndBuf = (1048576) Sets the socket send buffer size for the UDP socket, in bytes. -If unset, the default buffer size will be used by the operating system. +If set to zero, the default buffer size will be used by the operating system. +Note: this setting can have a significant impact on performance, especially raw throughput. + +@cindex UPnP +@item UPnP = (no) +If this option is enabled then tinc will search for UPnP-IGD devices on the local network. +It will then create and maintain port mappings for tinc's listening TCP and UDP ports. +If set to @samp{udponly}, tinc will only create a mapping for its UDP (data) port, not for its TCP (metaconnection) port. +Note that tinc must have been built with miniupnpc support for this feature to be available. +Furthermore, be advised that enabling this can have security implications, because the miniupnpc library that +tinc uses might not be well-hardened with regard to malicious UPnP replies. + +@cindex UPnPDiscoverWait +@item UPnPDiscoverWait = (5) +The amount of time to wait for replies when probing the local network for UPnP devices. + +@cindex UPnPRefreshPeriod +@item UPnPRefreshPeriod = (5) +How often tinc will re-add the port mapping, in case it gets reset on the UPnP device. +This also controls the duration of the port mapping itself, which will be set to twice that duration. @end table @@ -1143,11 +1308,12 @@ Multiple Address variables can be specified, in which case each address will be tried until a working connection has been established. @cindex Cipher -@item Cipher = <@var{cipher}> (aes-256-cbc) -The symmetric cipher algorithm used to encrypt UDP packets. +@item Cipher = <@var{cipher}> (blowfish) +The symmetric cipher algorithm used to encrypt UDP packets using the legacy protocol. Any cipher supported by LibreSSL or OpenSSL is recognized. -Furthermore, specifying "none" will turn off packet encryption. +Furthermore, specifying @samp{none} will turn off packet encryption. It is best to use only those ciphers which support CBC mode. +This option has no effect for connections using the SPTPS protocol, which always use AES-256-CTR. @cindex ClampMSS @item ClampMSS = (yes) @@ -1159,27 +1325,27 @@ Fragmentation Needed or Packet too Big messages are dropped by firewalls. @item Compression = <@var{level}> (0) This option sets the level of compression used for UDP packets. Possible values are 0 (off), 1 (fast zlib) and any integer up to 9 (best zlib), -10 (fast lzo) and 11 (best lzo). +10 (fast LZO) and 11 (best LZO). @cindex Digest -@item Digest = <@var{digest}> (sha256) -The digest algorithm used to authenticate UDP packets. +@item Digest = <@var{digest}> (sha1) +The digest algorithm used to authenticate UDP packets using the legacy protocol. Any digest supported by LibreSSL or OpenSSL is recognized. -Furthermore, specifying "none" will turn off packet authentication. +Furthermore, specifying @samp{none} will turn off packet authentication. +This option has no effect for connections using the SPTPS protocol, which always use HMAC-SHA-256. @cindex IndirectData @item IndirectData = (no) -This option specifies whether other tinc daemons besides the one you -specified with ConnectTo can make a direct connection to you. This is -especially useful if you are behind a firewall and it is impossible to -make a connection from the outside to your tinc daemon. Otherwise, it -is best to leave this option out or set it to no. +When set to yes, other nodes which do not already have a meta connection to you +will not try to establish direct communication with you. +It is best to leave this option out or set it to no. @cindex MACLength @item MACLength = <@var{bytes}> (4) -The length of the message authentication code used to authenticate UDP packets. +The length of the message authentication code used to authenticate UDP packets using the legacy protocol. Can be anything from 0 up to the length of the digest produced by the digest algorithm. +This option has no effect for connections using the SPTPS protocol, which never truncate MACs. @cindex PMTU @item PMTU = <@var{mtu}> (1514) @@ -1190,6 +1356,10 @@ This option controls the initial path MTU to this node. When this option is enabled, tinc will try to discover the path MTU to this node. After the path MTU has been discovered, it will be enforced on the VPN. +@cindex MTUInfoInterval +@item MTUInfoInterval = (5) +The minimum amount of time between sending periodic updates about relay path MTU. Useful for quickly determining MTU to indirect nodes. + @cindex Port @item Port = <@var{port}> (655) This is the port this tinc daemon listens on. @@ -1202,7 +1372,7 @@ This is the RSA public key for this host. @cindex PublicKeyFile @item PublicKeyFile = <@var{path}> [obsolete] This is the full path name of the RSA public key file that was generated -by @samp{tincd --generate-keys}. It must be a full path, not a relative +by @command{tinc generate-keys}. It must be a full path, not a relative directory. @cindex PEM format @@ -1237,7 +1407,6 @@ example: netmask 255.255.255.0 would become /24, 255.255.252.0 becomes /22. This conforms to standard CIDR notation as described in @uref{https://www.ietf.org/rfc/rfc1519.txt, RFC1519} -@cindex Subnet weight A Subnet can be given a weight to indicate its priority over identical Subnets owned by different nodes. The default weight is 10. Lower values indicate higher priority. Packets will be sent to the node with the highest priority, @@ -1245,15 +1414,18 @@ unless that node is not reachable, in which case the node with the next highest priority will be tried, and so on. @cindex TCPonly -@item TCPonly = (no) [deprecated] +@item TCPonly = (no) If this variable is set to yes, then the packets are tunnelled over a TCP connection instead of a UDP connection. This is especially useful for those who want to run a tinc daemon from behind a masquerading firewall, or if UDP packet routing is disabled somehow. Setting this options also implicitly sets IndirectData. -Since version 1.0.10, tinc will automatically detect whether communication via -UDP is possible or not. +@cindex Weight +@item Weight = +If this variable is set, it overrides the weight given to connections made with +another host. A higher weight means a lower priority is given to this +connection when broadcasting or forwarding packets. @end table @@ -1272,7 +1444,7 @@ this means that tinc will temporarily stop processing packets until the called s This guarantees that scripts will execute in the exact same order as the events that trigger them. If you need to run commands asynchronously, you have to ensure yourself that they are being run in the background. -Under Windows (not Cygwin), the scripts must have the extension .bat. +Under Windows, the scripts should have the extension @file{.bat} or @file{.cmd}. @table @file @cindex tinc-up @@ -1302,18 +1474,25 @@ This script is started when any host becomes reachable. This script is started when any host becomes unreachable. @item @value{sysconfdir}/tinc/@var{netname}/subnet-up -This script is started when a subnet becomes reachable. +This script is started when a Subnet becomes reachable. The Subnet and the node it belongs to are passed in environment variables. @item @value{sysconfdir}/tinc/@var{netname}/subnet-down -This script is started when a subnet becomes unreachable. +This script is started when a Subnet becomes unreachable. + +@item @value{sysconfdir}/tinc/@var{netname}/invitation-created +This script is started when a new invitation has been created. + +@item @value{sysconfdir}/tinc/@var{netname}/invitation-accepted +This script is started when an invitation has been used. + @end table @cindex environment variables The scripts are started without command line arguments, but can make use of certain environment variables. Under UNIX like operating systems the names of environment variables must be preceded by a $ in scripts. -Under Windows, in @file{.bat} files, they have to be put between % signs. +Under Windows, in @file{.bat} or @file{.cmd} files, they have to be put between % signs. @table @env @cindex NETNAME @@ -1355,57 +1534,122 @@ When a subnet becomes (un)reachable, this is set to the subnet. @item WEIGHT When a subnet becomes (un)reachable, this is set to the subnet weight. +@cindex INVITATION_FILE +@item INVITATION_FILE +When the @file{invitation-created} script is called, +this is set to the file where the invitation details will be stored. + +@cindex INVITATION_URL +@item INVITATION_URL +When the @file{invitation-created} script is called, +this is set to the invitation URL that has been created. @end table +Do not forget that under UNIX operating systems, +you have to make the scripts executable, using the command @command{chmod a+x script}. + @c ================================================================== @node How to configure @subsection How to configure -@subsubheading Step 1. Creating the main configuration file +@subsubheading Step 1. Creating initial configuration files. -The main configuration file will be called @file{@value{sysconfdir}/tinc/@var{netname}/tinc.conf}. -Adapt the following example to create a basic configuration file: +The initial directory structure, configuration files and public/private key pairs are created using the following command: @example -Name = @var{yourname} -Device = @file{/dev/tap0} +tinc -n @var{netname} init @var{name} @end example -Then, if you know to which other tinc daemon(s) yours is going to connect, -add `ConnectTo' values. - -@subsubheading Step 2. Creating your host configuration file - -If you added a line containing `Name = yourname' in the main configuration file, -you will need to create a host configuration file @file{@value{sysconfdir}/tinc/@var{netname}/hosts/yourname}. -Adapt the following example to create a host configuration file: +(You will need to run this as root, or use @command{sudo}.) +This will create the configuration directory @file{@value{sysconfdir}/tinc/@var{netname}.}, +and inside it will create another directory named @file{hosts/}. +In the configuration directory, it will create the file @file{tinc.conf} with the following contents: @example -Address = your.real.hostname.org -Subnet = 192.168.1.0/24 +Name = @var{name} @end example -You can also use an IP address instead of a hostname. -The `Subnet' specifies the address range that is local for @emph{your part of the VPN only}. -If you have multiple address ranges you can specify more than one `Subnet'. -You might also need to add a `Port' if you want your tinc daemon to run on a different port number than the default (655). +It will also create private RSA and Ed25519 keys, which will be stored in the files @file{rsa_key.priv} and @file{ed25519_key.priv}. +It will also create a host configuration file @file{hosts/@var{name}}, +which will contain the corresponding public RSA and Ed25519 keys. +Finally, on UNIX operating systems, it will create an executable script @file{tinc-up}, +which will initially not do anything except warning that you should edit it. -@c ================================================================== -@node Generating keypairs -@section Generating keypairs +@subsubheading Step 2. Modifying the initial configuration. -@cindex key generation -Now that you have already created the main configuration file and your host configuration file, -you can easily create a public/private keypair by entering the following command: +Unless you want to use tinc in switch mode, +you should now configure which range of addresses you will use on the VPN. +Let's assume you will be part of a VPN which uses the address range 192.168.0.0/16, +and you yourself have a smaller portion of that range: 192.168.2.0/24. +Then you should run the following command: @example -tincd -n @var{netname} -K +tinc -n @var{netname} add subnet 192.168.2.0/24 @end example -Tinc will generate a public and a private key and ask you where to put them. -Just press enter to accept the defaults. +This will add a Subnet statement to your host configuration file. +Try opening the file @file{@value{sysconfdir}/tinc/@var{netname}/hosts/@var{name}} in an editor. +You should now see a file containing the public RSA and Ed25519 keys (which looks like a bunch of random characters), +and the following line at the bottom: + +@example +Subnet = 192.168.2.0/24 +@end example + +If you will use more than one address range, you can add more Subnets. +For example, if you also use the IPv6 subnet fec0:0:0:2::/64, you can add it as well: + +@example +tinc -n @var{netname} add subnet fec0:0:0:2::/24 +@end example + +This will add another line to the file @file{hosts/@var{name}}. +If you make a mistake, you can undo it by simply using @samp{del} instead of @samp{add}. + +If you want other tinc daemons to create meta-connections to your daemon, +you should add your public IP address or hostname to your host configuration file. +For example, if your hostname is foo.example.org, run: + +@example +tinc -n @var{netname} add address foo.example.org +@end example + +@subsubheading Step 2. Exchanging configuration files. + +In order for two tinc daemons to be able to connect to each other, +they each need the other's host configuration files. +So if you want foo to be able to connect with bar, +You should send @file{hosts/@var{name}} to bar, and bar should send you his file which you should move to @file{hosts/bar}. +If you are on a UNIX platform, you can easily send an email containing the necessary information using the following command +(assuming the owner of bar has the email address bar@@example.org): + +@example +tinc -n @var{netname} export | mail -s "My config file" bar@@example.org +@end example + +If the owner of bar does the same to send his host configuration file to you, +you can probably pipe his email through the following command, +or you can just start this command in a terminal and copy&paste the email: + +@example +tinc -n @var{netname} import +@end example + +If you are the owner of bar yourself, and you have SSH access to that computer, +you can also swap the host configuration files using the following command: + +@example +tinc -n @var{netname} export \ + | ssh bar.example.org tinc -n @var{netname} exchange \ + | tinc -n @var{netname} import +@end example + +You can repeat this for a few other nodes as well. +It is not necessary to manually exchange host config files between all nodes; +after the initial connections are made tinc will learn about all the other nodes in the VPN, +and will automatically make other connections as necessary. @c ================================================================== @@ -1428,21 +1672,31 @@ You can configure the network interface by putting ordinary ifconfig, route, and to a script named @file{@value{sysconfdir}/tinc/@var{netname}/tinc-up}. When tinc starts, this script will be executed. When tinc exits, it will execute the script named @file{@value{sysconfdir}/tinc/@var{netname}/tinc-down}, but normally you don't need to create that script. +You can manually open the script in an editor, or use the following command: -An example @file{tinc-up} script: +@example +tinc -n @var{netname} edit tinc-up +@end example + +An example @file{tinc-up} script, that would be appropriate for the scenario in the previous section, is: @example #!/bin/sh -ifconfig $INTERFACE 192.168.1.1 netmask 255.255.0.0 +ifconfig $INTERFACE 192.168.2.1 netmask 255.255.0.0 +ip addr add fec0:0:0:2::/48 dev $INTERFACE @end example -This script gives the interface an IP address and a netmask. -The kernel will also automatically add a route to this interface, so normally you don't need +The first command gives the interface an IPv4 address and a netmask. +The kernel will also automatically add an IPv4 route to this interface, so normally you don't need to add route commands to the @file{tinc-up} script. The kernel will also bring the interface up after this command. @cindex netmask The netmask is the mask of the @emph{entire} VPN network, not just your own subnet. +The second command gives the interface an IPv6 address and netmask, +which will also automatically add an IPv6 route. +If you only want to use @command{ip addr} commands on Linux, don't forget that it doesn't bring the interface up, unlike ifconfig, +so you need to add @command{ip link set $INTERFACE up} in that case. The exact syntax of the ifconfig and route commands differs from platform to platform. You can look up the commands for setting addresses and adding routes in @ref{Platform specific information}, @@ -1482,6 +1736,9 @@ the real interface is also shown as a comment, to give you an idea of how these example host is set up. All branches use the netname `company' for this particular VPN. +Each branch is set up using the @command{tinc init} and @command{tinc config} commands, +here we just show the end results: + @subsubheading For Branch A @emph{BranchA} would be configured like this: @@ -1489,6 +1746,8 @@ for this particular VPN. In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.1.54.1 netmask 255.255.0.0 @@ -1499,7 +1758,6 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchA -Device = /dev/tap0 @end example On all hosts, @file{@value{sysconfdir}/tinc/company/hosts/BranchA} contains: @@ -1513,9 +1771,9 @@ Address = 1.2.3.4 -----END RSA PUBLIC KEY----- @end example -Note that the IP addresses of eth0 and tap0 are the same. +Note that the IP addresses of eth0 and the VPN interface are the same. This is quite possible, if you make sure that the netmasks of the interfaces are different. -It is in fact recommended to give both real internal network interfaces and tap interfaces the same IP address, +It is in fact recommended to give both real internal network interfaces and VPN interfaces the same IP address, since that will make things a lot easier to remember and set up. @@ -1524,6 +1782,8 @@ since that will make things a lot easier to remember and set up. In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.2.43.8 netmask 255.255.0.0 @@ -1534,12 +1794,10 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchB -ConnectTo = BranchA @end example Note here that the internal address (on eth0) doesn't have to be the -same as on the tap0 device. Also, ConnectTo is given so that this node will -always try to connect to BranchA. +same as on the VPN interface. On all hosts, in @file{@value{sysconfdir}/tinc/company/hosts/BranchB}: @@ -1558,6 +1816,8 @@ Address = 2.3.4.5 In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.3.69.254 netmask 255.255.0.0 @@ -1568,8 +1828,6 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchC -ConnectTo = BranchA -Device = /dev/tap1 @end example C already has another daemon that runs on port 655, so they have to @@ -1594,6 +1852,8 @@ Port = 2000 In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.4.3.32 netmask 255.255.0.0 @@ -1604,15 +1864,10 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchD -ConnectTo = BranchC -Device = /dev/net/tun @end example D will be connecting to C, which has a tincd running for this network on port 2000. It knows the port number from the host configuration file. -Also note that since D uses the tun/tap driver, the network interface -will not be called `tun' or `tap0' or something like that, but will -have the same name as netname. On all hosts, in @file{@value{sysconfdir}/tinc/company/hosts/BranchD}: @@ -1627,16 +1882,11 @@ Address = 4.5.6.7 @subsubheading Key files -A, B, C and D all have generated a public/private keypair with the following command: +A, B, C and D all have their own public/private key pairs: -@example -tincd -n company -K -@end example - -The private key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv}, -the public key is put into the host configuration file in the @file{@value{sysconfdir}/tinc/company/hosts/} directory. -During key generation, tinc automatically guesses the right filenames based on the -n option and -the Name directive in the @file{tinc.conf} file (if it is available). +The private RSA key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv}, +the private Ed25519 key is stored in @file{@value{sysconfdir}/tinc/company/ed25519_key.priv}, +and the public RSA and Ed25519 keys are put into the host configuration file in the @file{@value{sysconfdir}/tinc/company/hosts/} directory. @subsubheading Starting @@ -1653,7 +1903,7 @@ their daemons, tinc will try connecting until they are available. If everything else is done, you can start tinc by typing the following command: @example -tincd -n @var{netname} +tinc -n @var{netname} start @end example @cindex daemon @@ -1696,12 +1946,6 @@ This will also disable the automatic restart mechanism for fatal errors. Set debug level to @var{level}. The higher the debug level, the more gets logged. Everything goes via syslog. -@item -k, --kill[=@var{signal}] -Attempt to kill a running tincd (optionally with the specified @var{signal} instead of SIGTERM) and exit. -Use it in conjunction with the -n option to make sure you kill the right tinc daemon. -Under native Windows the optional argument is ignored, -the service will always be stopped and removed. - @item -n, --net=@var{netname} Use configuration for net @var{netname}. This will let tinc read all configuration files from @@ -1709,11 +1953,10 @@ This will let tinc read all configuration files from Specifying . for @var{netname} is the same as not specifying any @var{netname}. @xref{Multiple networks}. -@item -K, --generate-keys[=@var{bits}] -Generate public/private keypair of @var{bits} length. If @var{bits} is not specified, -2048 is the default. tinc will ask where you want to store the files, -but will default to the configuration directory (you can use the -c or -n option -in combination with -K). After that, tinc will quit. +@item --pidfile=@var{filename} +Store a cookie in @var{filename} which allows tinc to authenticate. +If unspecified, the default is +@file{@value{runstatedir}/tinc.@var{netname}.pid}. @item -o, --option=[@var{HOST}.]@var{KEY}=@var{VALUE} Without specifying a @var{HOST}, this will set server configuration variable @var{KEY} to @var{VALUE}. @@ -1725,6 +1968,8 @@ This option can be used more than once to specify multiple configuration variabl Lock tinc into main memory. This will prevent sensitive data like shared private keys to be written to the system swap files/partitions. +This option is not supported on all platforms. + @item --logfile[=@var{file}] Write log entries to a file instead of to the system logging facility. If @var{file} is omitted, the default is @file{@value{localstatedir}/log/tinc.@var{netname}.log}. @@ -1752,11 +1997,14 @@ you must copy @file{/etc/resolv.conf} into the chroot directory. If you want to be able to run scripts other than @file{tinc-up} in the chroot, you must ensure the appropriate shell is also installed in the chroot, along with all its dependencies. +This option is not supported on all platforms. @item -U, --user=@var{user} Switch to the given @var{user} after initialization, at the same time as chroot is performed (see --chroot above). With this option tinc drops privileges, for added security. +This option is not supported on all platforms. + @item --help Display a short reminder of these runtime options and terminate. @@ -1789,19 +2037,6 @@ New outgoing connections specified in @file{tinc.conf} will be made. If the --logfile option is used, this will also close and reopen the log file, useful when log rotation is used. -@item INT -Temporarily increases debug level to 5. -Send this signal again to revert to the original level. - -@item USR1 -Dumps the connection list to syslog. - -@item USR2 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog. - -@item WINCH -Purges all information remembered about unreachable nodes. - @end table @c ================================================================== @@ -1865,7 +2100,7 @@ Do you have a firewall or a NAT device (a masquerading firewall or perhaps an AD If so, check that it allows TCP and UDP traffic on port 655. If it masquerades and the host running tinc is behind it, make sure that it forwards TCP and UDP traffic to port 655 to the host running tinc. You can add @samp{TCPOnly = yes} to your host config file to force tinc to only use a single TCP connection, -this works through most firewalls and NATs. Since version 1.0.10, tinc will automatically fall back to TCP if direct communication via UDP is not possible. +this works through most firewalls and NATs. @end itemize @@ -1903,7 +2138,7 @@ Some of them will only be visible if the debug level is high enough. @item Error reading RSA key file `rsa_key.priv': No such file or directory @itemize -@item You forgot to create a public/private keypair. +@item You forgot to create a public/private key pair. @item Specify the complete pathname to the private key file with the @samp{PrivateKeyFile} option. @end itemize @@ -1964,13 +2199,15 @@ or if that is not the case, try changing the prefix length into /32. @itemize @item If you see this only sporadically, it is harmless and caused by a node sending packets using an old key. +@item If you see this often and another node is not reachable anymore, then a NAT (masquerading firewall) is changing the source address of UDP packets. +You can add @samp{TCPOnly = yes} to host configuration files to force all VPN traffic to go over a TCP connection. @end itemize @item Got bad/bogus/unauthorized REQUEST from foo (1.2.3.4 port 12345) @itemize -@item Node foo does not have the right public/private keypair. -Generate new keypairs and distribute them again. +@item Node foo does not have the right public/private key pair. +Generate new key pairs and distribute them again. @item An attacker tries to gain access to your VPN. @item A network error caused corruption of metadata sent from foo. @end itemize @@ -1990,10 +2227,530 @@ Be sure to include the following information in your bugreport: @item What platform (operating system, version, hardware architecture) and which version of tinc you use. @item If compiling tinc fails, a copy of @file{config.log} and the error messages you get. @item Otherwise, a copy of @file{tinc.conf}, @file{tinc-up} and all files in the @file{hosts/} directory. -@item The output of the commands @samp{ifconfig -a} and @samp{route -n} (or @samp{netstat -rn} if that doesn't work). +@item The output of the commands @command{ifconfig -a} and @command{route -n} (or @command{netstat -rn} if that doesn't work). @item The output of any command that fails to work as it should (like ping or traceroute). @end itemize +@c ================================================================== +@node Controlling tinc +@chapter Controlling tinc + +@cindex command line interface +You can start, stop, control and inspect a running tincd through the tinc +command. A quick example: + +@example +tinc -n @var{netname} reload +@end example + +@cindex shell +If tinc is started without a command, it will act as a shell; it will display a +prompt, and commands can be entered on the prompt. If tinc is compiled with +libreadline, history and command completion are available on the prompt. One +can also pipe a script containing commands through tinc. In that case, lines +starting with a # symbol will be ignored. + +@menu +* tinc runtime options:: +* tinc environment variables:: +* tinc commands:: +* tinc examples:: +* tinc top:: +@end menu + + +@c ================================================================== +@node tinc runtime options +@section tinc runtime options + +@c from the manpage +@table @option +@item -c, --config=@var{path} +Read configuration options from the directory @var{path}. The default is +@file{@value{sysconfdir}/tinc/@var{netname}/}. + +@item -n, --net=@var{netname} +Use configuration for net @var{netname}. @xref{Multiple networks}. + +@item --pidfile=@var{filename} +Use the cookie from @var{filename} to authenticate with a running tinc daemon. +If unspecified, the default is +@file{@value{runstatedir}/tinc.@var{netname}.pid}. + +@cindex batch +@item -b, --batch +Don't ask for anything (non-interactive mode). + +@item --force +Force some commands to work despite warnings. + +@item --help +Display a short reminder of runtime options and commands, then terminate. + +@item --version +Output version information and exit. + +@end table + +@c ================================================================== +@node tinc environment variables +@section tinc environment variables + +@table @env +@cindex NETNAME +@item NETNAME +If no netname is specified on the command line with the @option{-n} option, +the value of this environment variable is used. +@end table + +@c ================================================================== +@node tinc commands +@section tinc commands + +@c from the manpage +@table @samp + +@cindex init +@item init [@var{name}] +Create initial configuration files and RSA and Ed25519 key pairs with default length. +If no @var{name} for this node is given, it will be asked for. + +@cindex get +@item get @var{variable} +Print the current value of configuration variable @var{variable}. +If more than one variable with the same name exists, +the value of each of them will be printed on a separate line. + +@cindex set +@item set @var{variable} @var{value} +Set configuration variable @var{variable} to the given @var{value}. +All previously existing configuration variables with the same name are removed. +To set a variable for a specific host, use the notation @var{host}.@var{variable}. + +@cindex add +@item add @var{variable} @var{value} +As above, but without removing any previously existing configuration variables. +If the variable already exists with the given value, nothing happens. + +@cindex del +@item del @var{variable} [@var{value}] +Remove configuration variables with the same name and @var{value}. +If no @var{value} is given, all configuration variables with the same name will be removed. + +@cindex edit +@item edit @var{filename} +Start an editor for the given configuration file. +You do not need to specify the full path to the file. + +@cindex export +@item export +Export the host configuration file of the local node to standard output. + +@cindex export-all +@item export-all +Export all host configuration files to standard output. + +@cindex import +@item import +Import host configuration file(s) generated by the tinc export command from standard input. +Already existing host configuration files are not overwritten unless the option --force is used. + +@cindex exchange +@item exchange +The same as export followed by import. + +@cindex exchange-all +@item exchange-all +The same as export-all followed by import. + +@cindex invite +@item invite @var{name} +Prepares an invitation for a new node with the given @var{name}, +and prints a short invitation URL that can be used with the join command. + +@cindex join +@item join [@var{URL}] +Join an existing VPN using an invitation URL created using the invite command. +If no @var{URL} is given, it will be read from standard input. + +@cindex start +@item start [tincd options] +Start @command{tincd}, optionally with the given extra options. + +@cindex stop +@item stop +Stop @command{tincd}. + +@cindex restart +@item restart [tincd options] +Restart @command{tincd}, optionally with the given extra options. + +@cindex reload +@item reload +Partially rereads configuration files. Connections to hosts whose host +config files are removed are closed. New outgoing connections specified +in @file{tinc.conf} will be made. + +@cindex pid +@item pid +Shows the PID of the currently running @command{tincd}. + +@cindex generate-keys +@item generate-keys [@var{bits}] +Generate both RSA and Ed25519 key pairs (see below) and exit. +tinc will ask where you want to store the files, but will default to the +configuration directory (you can use the -c or -n option). + +@cindex generate-ed25519-keys +@item generate-ed25519-keys +Generate public/private Ed25519 key pair and exit. + +@cindex generate-rsa-keys +@item generate-rsa-keys [@var{bits}] +Generate public/private RSA key pair and exit. If @var{bits} is omitted, the +default length will be 2048 bits. When saving keys to existing files, tinc +will not delete the old keys; you have to remove them manually. + +@cindex dump +@item dump [reachable] nodes +Dump a list of all known nodes in the VPN. +If the reachable keyword is used, only lists reachable nodes. + +@item dump edges +Dump a list of all known connections in the VPN. + +@item dump subnets +Dump a list of all known subnets in the VPN. + +@item dump connections +Dump a list of all meta connections with ourself. + +@cindex graph +@item dump graph | digraph +Dump a graph of the VPN in dotty format. +Nodes are colored according to their reachability: +red nodes are unreachable, orange nodes are indirectly reachable, green nodes are directly reachable. +Black nodes are either directly or indirectly reachable, but direct reachability has not been tried yet. + +@item dump invitations +Dump a list of outstanding invitations. +The filename of the invitation, as well as the name of the node that is being invited is shown for each invitation. + +@cindex info +@item info @var{node} | @var{subnet} | @var{address} +Show information about a particular @var{node}, @var{subnet} or @var{address}. +If an @var{address} is given, any matching subnet will be shown. + +@cindex purge +@item purge +Purges all information remembered about unreachable nodes. + +@cindex debug +@item debug @var{level} +Sets debug level to @var{level}. + +@cindex log +@item log [@var{level}] +Capture log messages from a running tinc daemon. +An optional debug level can be given that will be applied only for log messages sent to tinc. + +@cindex retry +@item retry +Forces tinc to try to connect to all uplinks immediately. +Usually tinc attempts to do this itself, +but increases the time it waits between the attempts each time it failed, +and if tinc didn't succeed to connect to an uplink the first time after it started, +it defaults to the maximum time of 15 minutes. + +@cindex disconnect +@item disconnect @var{node} +Closes the meta connection with the given @var{node}. + +@cindex top +@item top +If tinc is compiled with libcurses support, this will display live traffic statistics for all the known nodes, +similar to the UNIX top command. +See below for more information. + +@cindex pcap +@item pcap +Dump VPN traffic going through the local tinc node in pcap-savefile format to standard output, +from where it can be redirected to a file or piped through a program that can parse it directly, +such as tcpdump. + +@cindex network +@item network [@var{netname}] +If @var{netname} is given, switch to that network. +Otherwise, display a list of all networks for which configuration files exist. + +@cindex fsck +@item fsck +This will check the configuration files for possible problems, +such as unsafe file permissions, missing executable bit on script, +unknown and obsolete configuration variables, wrong public and/or private keys, and so on. + +When problems are found, this will be printed on a line with WARNING or ERROR in front of it. +Most problems must be corrected by the user itself, however in some cases (like file permissions and missing public keys), +tinc will ask if it should fix the problem. + +@cindex sign +@item sign [@var{filename}] +Sign a file with the local node's private key. +If no @var{filename} is given, the file is read from standard input. +The signed file is written to standard output. + +@cindex verify +@item verify @var{name} [@var{filename}] + +Check the signature of a file against a node's public key. +The @var{name} of the node must be given, +or can be @samp{.} to check against the local node's public key, +or @samp{*} to allow a signature from any node whose public key is known. +If no @var{filename} is given, the file is read from standard input. +If the verification is successful, a copy of the input with the signature removed is written to standard output, and the exit code will be zero. +If the verification failed, nothing will be written to standard output, and the exit code will be non-zero. + +@end table + +@c ================================================================== +@node tinc examples +@section tinc examples + +Examples of some commands: + +@example +tinc -n vpn dump graph | circo -Txlib +tinc -n vpn pcap | tcpdump -r - +tinc -n vpn top +@end example + +Examples of changing the configuration using tinc: + +@example +tinc -n vpn init foo +tinc -n vpn add Subnet 192.168.1.0/24 +tinc -n vpn add bar.Address bar.example.com +tinc -n vpn set Mode switch +tinc -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@@example.com +@end example + +@c ================================================================== +@node tinc top +@section tinc top + +@cindex top +The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters. +It displays a list of all the known nodes in the left-most column, +and the amount of bytes and packets read from and sent to each node in the other columns. +By default, the information is updated every second. +The behaviour of the top command can be changed using the following keys: + +@table @key + +@item s +Change the interval between updates. +After pressing the @key{s} key, enter the desired interval in seconds, followed by enter. +Fractional seconds are honored. +Intervals lower than 0.1 seconds are not allowed. + +@item c +Toggle between displaying current traffic rates (in packets and bytes per second) +and cumulative traffic (total packets and bytes since the tinc daemon started). + +@item n +Sort the list of nodes by name. + +@item i +Sort the list of nodes by incoming amount of bytes. + +@item I +Sort the list of nodes by incoming amount of packets. + +@item o +Sort the list of nodes by outgoing amount of bytes. + +@item O +Sort the list of nodes by outgoing amount of packets. + +@item t +Sort the list of nodes by sum of incoming and outgoing amount of bytes. + +@item T +Sort the list of nodes by sum of incoming and outgoing amount of packets. + +@item b +Show amount of traffic in bytes. + +@item k +Show amount of traffic in kilobytes. + +@item M +Show amount of traffic in megabytes. + +@item G +Show amount of traffic in gigabytes. + +@item q +Quit. + +@end table + + +@c ================================================================== +@node Invitations +@chapter Invitations + +Invitations are an easy way to add new nodes to an existing VPN. Invitations +can be created on an existing node using the @command{tinc invite} command, which +generates a relatively short URL which can be given to someone else, who uses +the @command{tinc join} command to automatically set up tinc so it can connect to +the inviting node. The next sections describe how invitations actually work, +and how to further automate the invitations. + +@menu +* How invitations work:: +* Invitation file format:: +* Writing an invitation-created script:: +@end menu + + +@c ================================================================== +@node How invitations work +@section How invitations work + +When an invitation is created on a node (which from now on we will call the +server) using the @command{tinc invite} command, an invitation file is created +that contains all the information necessary for the invitee (which we will call +the client) to create its configuration files. The invitation file is stays on +the server, but a URL is generated that has enough information for the client +to contact the server and to retrieve the invitation file. The whole URL is +around 80 characters long and looks like this: + +@example +server.example.org:12345/cW1NhLHS-1WPFlcFio8ztYHvewTTKYZp8BjEKg3vbMtDz7w4 +@end example + +It is composed of four parts: + +@example +hostname : port / keyhash cookie +@end example + +The hostname and port tell the client how to reach the tinc daemon on the server. +The part after the slash looks like one blob, but is composed of two parts. +The keyhash is the hash of the public key of the server. +The cookie is a shared secret that identifies the client to the server. + +When the client connects to the server in order to join the VPN, the client and +server will exchange temporary public keys. The client verifies that the hash +of the server's public key matches the keyhash from the invitation URL. If +not, it will immediately exit with an error. Otherwise, an ECDH exchange will +happen so the client and server can communicate privately with each other. The +client will then present the cookie to the server. The server uses this to +look up the corresponding invitation file it generated earlier. If it exists, +it will send the invitation file to the client. The client will also create a +permanent public key, and send it to the server. After the exchange is +completed, the connection is broken. The server creates a host config file for +the client containing the client's permanent public key, and the client creates +tinc.conf, host config files and possibly a tinc-up script based on the +information in the invitation file. + +It is important that the invitation URL is kept secret until it is used; if +another person gets a copy of the invitation URL before the real client runs +the @command{tinc join} command, then that other person can try to join the VPN. + + +@c ================================================================== +@node Invitation file format +@section Invitation file format + +The contents of an invitation file that is generated by the @command{tinc invite} +command looks like this: + +@example +Name = client +Netname = vpn +ConnectTo = server +#-------------------------------------# +Name = server +Ed25519PublicKey = augbnwegoij123587... +Address = server.example.com +@end example + +The file is basically a concatenation of several host config blocks. Each host +config block starts with @samp{Name = ...}. Lines that look like @samp{#---#} +are not important, it just makes it easier for humans to read the file. +However, the first line of an invitation file @emph{must} always start with +@samp{Name = ...}. + +The first host config block is always the one representing the invitee. So the +first Name statement determines the name that the invitee will get. From the +first block, the @file{tinc.conf} and @file{hosts/client} files will be +generated; the @command{tinc join} command on the client will automatically +separate statements based on whether they should be in @file{tinc.conf} or in a +host config file. Some statements are special and are treated differently: + +@table @asis +@item Netname = <@var{netname}> +This is a hint to the invitee which netname to use for the VPN. It is used if +the invitee did not already specify a netname, and if there is no pre-existing +configuration with the same netname. + +@cindex Ifconfig +@item Ifconfig = <@var{address}[/@var{netmask}] | dhcp | dhcp6 | slaac> +This is a hint for generating a @file{tinc-up} script. +If an address is specified, a command will be added to @file{tinc-up} so the VPN interface will be configured to have the given address. +If it is the word @samp{dhcp}, a command will be added to start a DHCP client on the VPN interface. +If it is the word @samp{dhcpv6}, it will be a DHCPv6 client. +If it is @samp{slaac}, then it will add commands to enable IPv6 stateless address autoconfiguration. +It is also possible to specify a MAC address, in which case a command will be added to set the MAC address of the VPN interface. + +The exact commands added to the @file{tinc-up} script depends on the operating system the client is using. +Multiple Ifconfig statements can be specified, however one should only use one Ifconfig statement per address family. + +@cindex Route +@item Route = <@var{address}[/@var{netmask}]> [<@var{gateway}>] +This is a hint for generating a @file{tinc-up} script. +Route statements are similar to Ifconfig statements, but add routes instead of addresses. +These only allow IPv4 and IPv6 routes. +If no gateway address is specified, the route is directed to the VPN interface. +In general, a gateway is only necessary when running tinc in switch mode. +@end table + +Subsequent host config blocks are copied verbatim into their respective files +in @file{hosts/}. The invitation file generated by @command{tinc invite} will +normally only contain two blocks; one for the client and one for the server. + + +@c ================================================================== +@node Writing an invitation-created script +@section Writing an invitation-created script + +When an invitation is generated, the @file{invitation-created} script is called (if +it exists) right after the invitation file is written, but before the URL has +been written to stdout. This allows one to change the invitation file +automatically before the invitation URL is passed to the invitee. Here is an +example shell script that approximately recreates the default invitation file: + +@example +#!/bin/sh + +cat >$INVITATION_FILE <>$INVITATION_FILE +@end example + +You can add more ConnectTo statements, and change `tinc export` to `tinc +export-all` for example. But you can also use the script to automatically hand +out a Subnet to the invitee. Note that the script doesn't have to be a shell script, +you can use any language, it just has to be executable. + + @c ================================================================== @node Technical information @chapter Technical information @@ -2035,7 +2792,7 @@ There are two possible types of virtual network devices: and `tap' style, which are Ethernet devices and handle complete Ethernet frames. So when tinc reads an Ethernet frame from the device, it determines its -type. When tinc is in its default routing mode, it can handle IPv4 and IPv6 +type. When tinc is in it's default routing mode, it can handle IPv4 and IPv6 packets. Depending on the Subnet lines, it will send the packets off to their destination IP address. In the `switch' and `hub' mode, tinc will use broadcasts and MAC address discovery to deduce the destination of the packets. @@ -2066,7 +2823,7 @@ If the virtual network device is a `tun' device (a point-to-point tunnel), there is no problem for the kernel to accept a packet. However, if it is a `tap' device (this is the only available type on FreeBSD), the destination MAC address must match that of the virtual network interface. -If tinc is in its default routing mode, ARP does not work, so the correct destination MAC +If tinc is in it's default routing mode, ARP does not work, so the correct destination MAC can not be known by the sending host. Tinc solves this by letting the receiving end detect the MAC address of its own virtual network interface and overwriting the destination MAC address of the received packet. @@ -2128,7 +2885,7 @@ daemon started with the --bypass-security option and to read and write requests by hand, provided that one understands the numeric codes sent. -The authentication scheme is described in @ref{Authentication protocol}. After a +The authentication scheme is described in @ref{Security}. After a successful authentication, the server and the client will exchange all the information about other tinc daemons and subnets they know of, so that both sides (and all the other tinc daemons behind them) have their information @@ -2215,10 +2972,10 @@ destination. @cindex PING @cindex PONG @example -daemon message +daemon message ------------------------------------------------------------------ -origin PING -dest. PONG +origin PING +dest. PONG ------------------------------------------------------------------ @end example @@ -2246,31 +3003,30 @@ the tinc project after TINC. @cindex SVPN But in order to be ``immune'' to eavesdropping, you'll have to encrypt -your data. Because tinc is a @emph{Secure} VPN (SVPN) daemon, it does +your data. Because tinc is a @emph{Secure} VPN (SVPN) daemon, it does exactly that: encrypt. -Tinc by default uses blowfish encryption with 128 bit keys in CBC mode, 32 bit -sequence numbers and 4 byte long message authentication codes to make sure -eavesdroppers cannot get and cannot change any information at all from the -packets they can intercept. The encryption algorithm and message authentication -algorithm can be changed in the configuration. The length of the message -authentication codes is also adjustable. The length of the key for the -encryption algorithm is always the default length used by LibreSSL/OpenSSL. +However, encryption in itself does not prevent an attacker from modifying the encrypted data. +Therefore, tinc also authenticates the data. +Finally, tinc uses sequence numbers (which themselves are also authenticated) to prevent an attacker from replaying valid packets. + +Since version 1.1pre3, tinc has two protocols used to protect your data; the legacy protocol, and the new Simple Peer-to-Peer Security (SPTPS) protocol. +The SPTPS protocol is designed to address some weaknesses in the legacy protocol. +The new authentication protocol is used when two nodes connect to each other that both have the ExperimentalProtocol option set to yes, +otherwise the legacy protocol will be used. @menu -* Authentication protocol:: +* Legacy authentication protocol:: +* Simple Peer-to-Peer Security:: * Encryption of network packets:: * Security issues:: @end menu @c ================================================================== -@node Authentication protocol -@subsection Authentication protocol +@node Legacy authentication protocol +@subsection Legacy authentication protocol -@cindex authentication -A new scheme for authentication in tinc has been devised, which offers some -improvements over the protocol used in 1.0pre2 and 1.0pre3. Explanation is -below. +@cindex legacy authentication protocol @cindex ID @cindex META_KEY @@ -2284,28 +3040,50 @@ client server -client ID client 12 - | +---> version - +-------> name of tinc daemon +client ID client 17.2 + | | +-> minor protocol version + | +----> major protocol version + +--------> name of tinc daemon -server ID server 12 - | +---> version - +-------> name of tinc daemon +server ID server 17.2 + | | +-> minor protocol version + | +----> major protocol version + +--------> name of tinc daemon -client META_KEY 5f0823a93e35b69e...7086ec7866ce582b - \_________________________________/ - +-> RSAKEYLEN bits totally random string S1, - encrypted with server's public RSA key +client META_KEY 94 64 0 0 5f0823a93e35b69e...7086ec7866ce582b + | | | | \_________________________________/ + | | | | +-> RSAKEYLEN bits totally random string S1, + | | | | encrypted with server's public RSA key + | | | +-> compression level + | | +---> MAC length + | +------> digest algorithm NID + +---------> cipher algorithm NID -server META_KEY 6ab9c1640388f8f0...45d1a07f8a672630 - \_________________________________/ - +-> RSAKEYLEN bits totally random string S2, - encrypted with client's public RSA key +server META_KEY 94 64 0 0 6ab9c1640388f8f0...45d1a07f8a672630 + | | | | \_________________________________/ + | | | | +-> RSAKEYLEN bits totally random string S2, + | | | | encrypted with client's public RSA key + | | | +-> compression level + | | +---> MAC length + | +------> digest algorithm NID + +---------> cipher algorithm NID +-------------------------------------------------------------------------- +@end example + +The protocol allows each side to specify encryption algorithms and parameters, +but in practice they are always fixed, since older versions of tinc did not +allow them to be different from the default values. The cipher is always +Blowfish in OFB mode, the digest is SHA1, but the MAC length is zero and no +compression is used. From now on: - - the client will symmetrically encrypt outgoing traffic using S1 - - the server will symmetrically encrypt outgoing traffic using S2 +@itemize +@item the client will symmetrically encrypt outgoing traffic using S1 +@item the server will symmetrically encrypt outgoing traffic using S2 +@end itemize +@example +-------------------------------------------------------------------------- client CHALLENGE da02add1817c1920989ba6ae2a49cecbda0 \_________________________________/ +-> CHALLEN bits totally random string H1 @@ -2325,57 +3103,188 @@ their identity. Further information is exchanged. client ACK 655 123 0 | | +-> options - | +----> estimated weight - +--------> listening port of client + | +----> estimated weight + +--------> listening port of client server ACK 655 321 0 | | +-> options - | +----> estimated weight - +--------> listening port of server + | +----> estimated weight + +--------> listening port of server -------------------------------------------------------------------------- @end example -This new scheme has several improvements, both in efficiency and security. +This legacy authentication protocol has several weaknesses, pointed out by security export Peter Gutmann. +First, data is encrypted with RSA without padding. +Padding schemes are designed to prevent attacks when the size of the plaintext is not equal to the size of the RSA key. +Tinc always encrypts random nonces that have the same size as the RSA key, so we do not believe this leads to a break of the security. +There might be timing or other side-channel attacks against RSA encryption and decryption, tinc does not employ any protection against those. +Furthermore, both sides send identical messages to each other, there is no distinction between server and client, +which could make a MITM attack easier. +However, no exploit is known in which a third party who is not already trusted by other nodes in the VPN could gain access. +Finally, the RSA keys are used to directly encrypt the session keys, which means that if the RSA keys are compromised, it is possible to decrypt all previous VPN traffic. +In other words, the legacy protocol does not provide perfect forward secrecy. -First of all, the server sends exactly the same kind of messages over the wire -as the client. The previous versions of tinc first authenticated the client, -and then the server. This scheme even allows both sides to send their messages -simultaneously, there is no need to wait for the other to send something first. -This means that any calculations that need to be done upon sending or receiving -a message can also be done in parallel. This is especially important when doing -RSA encryption/decryption. Given that these calculations are the main part of -the CPU time spent for the authentication, speed is improved by a factor 2. +@c ================================================================== +@node Simple Peer-to-Peer Security +@subsection Simple Peer-to-Peer Security +@cindex SPTPS -Second, only one RSA encrypted message is sent instead of two. This reduces the -amount of information attackers can see (and thus use for a cryptographic -attack). It also improves speed by a factor two, making the total speedup a -factor 4. +The SPTPS protocol is designed to address the weaknesses in the legacy protocol. +SPTPS is based on TLS 1.2, but has been simplified: there is no support for exchanging public keys, and there is no cipher suite negotiation. +Instead, SPTPS always uses a very strong cipher suite: +peers authenticate each other using 521 bits ECC keys, +Diffie-Hellman using ephemeral 521 bits ECC keys is used to provide perfect forward secrecy (PFS), +AES-256-CTR is used for encryption, and HMAC-SHA-256 for message authentication. -Third, and most important: -The symmetric cipher keys are exchanged first, the challenge is done -afterwards. In the previous authentication scheme, because a man-in-the-middle -could pass the challenge/chal_reply phase (by just copying the messages between -the two real tinc daemons), but no information was exchanged that was really -needed to read the rest of the messages, the challenge/chal_reply phase was of -no real use. The man-in-the-middle was only stopped by the fact that only after -the ACK messages were encrypted with the symmetric cipher. Potentially, it -could even send it's own symmetric key to the server (if it knew the server's -public key) and read some of the metadata the server would send it (it was -impossible for the mitm to read actual network packets though). The new scheme -however prevents this. +Similar to TLS, messages are split up in records. +A complete logical record contains the following information: -This new scheme makes sure that first of all, symmetric keys are exchanged. The -rest of the messages are then encrypted with the symmetric cipher. Then, each -side can only read received messages if they have their private key. The -challenge is there to let the other side know that the private key is really -known, because a challenge reply can only be sent back if the challenge is -decrypted correctly, and that can only be done with knowledge of the private -key. +@itemize +@item uint32_t seqno (network byte order) +@item uint16_t length (network byte order) +@item uint8_t type +@item opaque data[length] +@item opaque hmac[HMAC_SIZE] (HMAC over all preceding fields) +@end itemize -Fourth: the first thing that is sent via the symmetric cipher encrypted -connection is a totally random string, so that there is no known plaintext (for -an attacker) in the beginning of the encrypted stream. +Depending on whether SPTPS records are sent via TCP or UDP, either the seqno or the length field is omitted on the wire +(but they are still included in the calculation of the HMAC); +for TCP packets are guaranteed to arrive in-order so we can infer the seqno, but packets can be split or merged, so we still need the length field to determine the boundaries between records; +for UDP packets we know that there is exactly one record per packet, and we know the length of a packet, but packets can be dropped, duplicated and/or reordered, so we need to include the seqno. +The type field is used to distinguish between application records or handshake records. +Types 0 to 127 are application records, type 128 is a handshake record, and types 129 to 255 are reserved. + +Before the initial handshake, no fields are encrypted, and the HMAC field is not present. +After the authentication handshake, the length (if present), type and data fields are encrypted, and the HMAC field is present. +For UDP packets, the seqno field is not encrypted, as it is used to determine the value of the counter used for encryption. + +The authentication consists of an exchange of Key EXchange, SIGnature and ACKnowledge messages, transmitted using type 128 records. + +Overview: + +@example +Initiator Responder +--------------------- +KEX -> + <- KEX +SIG -> + <- SIG + +...encrypt and HMAC using session keys from now on... + +App -> + <- App +... + ... + +...key renegotiation starts here... + +KEX -> + <- KEX +SIG -> + <- SIG +ACK -> + <- ACK + +...encrypt and HMAC using new session keys from now on... + +App -> + <- App +... + ... +--------------------- +@end example + +Note that the responder does not need to wait before it receives the first KEX message, +it can immediately send its own once it has accepted an incoming connection. + +Key EXchange message: + +@itemize +@item uint8_t kex_version (always 0 in this version of SPTPS) +@item opaque nonce[32] (random number) +@item opaque ecdh_key[ECDH_SIZE] +@end itemize + +SIGnature message: + +@itemize +@item opaque ecdsa_signature[ECDSA_SIZE] +@end itemize + +ACKnowledge message: + +@itemize +@item empty (only sent after key renegotiation) +@end itemize + +Remarks: + +@itemize +@item At the start, both peers generate a random nonce and an Elliptic Curve public key and send it to the other in the KEX message. +@item After receiving the other's KEX message, both KEX messages are concatenated (see below), + and the result is signed using ECDSA. + The result is sent to the other. +@item After receiving the other's SIG message, the signature is verified. + If it is correct, the shared secret is calculated from the public keys exchanged in the KEX message using the Elliptic Curve Diffie-Helman algorithm. +@item The shared secret key is expanded using a PRF. + Both nonces and the application specific label are also used as input for the PRF. +@item An ACK message is sent only when doing key renegotiation, and is sent using the old encryption keys. +@item The expanded key is used to key the encryption and HMAC algorithms. +@end itemize + +The signature is calculated over this string: + +@itemize +@item uint8_t initiator (0 = local peer, 1 = remote peer is initiator) +@item opaque remote_kex_message[1 + 32 + ECDH_SIZE] +@item opaque local_kex_message[1 + 32 + ECDH_SIZE] +@item opaque label[label_length] +@end itemize + +The PRF is calculated as follows: + +@itemize +@item A HMAC using SHA512 is used, the shared secret is used as the key. +@item For each block of 64 bytes, a HMAC is calculated. For block n: hmac[n] = + HMAC_SHA512(hmac[n - 1] + seed) +@item For the first block (n = 1), hmac[0] is given by HMAC_SHA512(zeroes + seed), + where zeroes is a block of 64 zero bytes. +@end itemize + +The seed is as follows: + +@itemize +@item const char[13] "key expansion" +@item opaque responder_nonce[32] +@item opaque initiator_nonce[32] +@item opaque label[label_length] +@end itemize + +The expanded key is used as follows: + +@itemize +@item opaque responder_cipher_key[CIPHER_KEYSIZE] +@item opaque responder_digest_key[DIGEST_KEYSIZE] +@item opaque initiator_cipher_key[CIPHER_KEYSIZE] +@item opaque initiator_digest_key[DIGEST_KEYSIZE] +@end itemize + +Where initiator_cipher_key is the key used by session initiator to encrypt +messages sent to the responder. + +When using 256 bits Ed25519 keys, the AES-256-CTR cipher and HMAC-SHA-256 digest algorithm, +the sizes are as follows: + +@example +ECDH_SIZE: 32 (= 256/8) +ECDSA_SIZE: 64 (= 2 * 256/8) +CIPHER_KEYSIZE: 48 (= 256/8 + 128/8) +DIGEST_KEYSIZE: 32 (= 256/8) +@end example + +Note that the cipher key also includes the initial value for the counter. @c ================================================================== @node Encryption of network packets @@ -2385,11 +3294,11 @@ an attacker) in the beginning of the encrypted stream. A data packet can only be sent if the encryption key is known to both parties, and the connection is activated. If the encryption key is not known, a request is sent to the destination using the meta connection -to retrieve it. The packet is stored in a queue while waiting for the -key to arrive. +to retrieve it. @cindex UDP -The UDP packet containing the network packet from the VPN has the following layout: +The UDP packets can be either encrypted with the legacy protocol or with SPTPS. +In case of the legacy protocol, the UDP packet containing the network packet from the VPN has the following layout: @example ... | IP header | UDP header | seqno | VPN packet | MAC | UDP trailer @@ -2399,12 +3308,38 @@ The UDP packet containing the network packet from the VPN has the following layo Encrypted with symmetric cipher @end example + + + So, the entire VPN packet is encrypted using a symmetric cipher, including a 32 bits sequence number that is added in front of the actual VPN packet, to act as a unique IV for each packet and to prevent replay attacks. A message authentication code -is added to the UDP packet to prevent alteration of packets. By default the -first 4 bytes of the digest are used for this, but this can be changed using -the MACLength configuration variable. +is added to the UDP packet to prevent alteration of packets. +Tinc by default encrypts network packets using Blowfish with 128 bit keys in CBC mode +and uses 4 byte long message authentication codes to make sure +eavesdroppers cannot get and cannot change any information at all from the +packets they can intercept. The encryption algorithm and message authentication +algorithm can be changed in the configuration. The length of the message +authentication codes is also adjustable. The length of the key for the +encryption algorithm is always the default length used by LibreSSL/OpenSSL. + +The SPTPS protocol is described in @ref{Simple Peer-to-Peer Security}. +For comparison, this is how SPTPS UDP packets look: + +@example +... | IP header | UDP header | seqno | type | VPN packet | MAC | UDP trailer + \__________________/\_____/ + | | + V +---> digest algorithm + Encrypted with symmetric cipher +@end example + +The difference is that the seqno is not encrypted, since the encryption cipher is used in CTR mode, +and therefore the seqno must be known before the packet can be decrypted. +Furthermore, the MAC is never truncated. +The SPTPS protocol always uses the AES-256-CTR cipher and HMAC-SHA-256 digest, +this cannot be changed. + @c ================================================================== @node Security issues @@ -2427,8 +3362,24 @@ On the 15th of September 2003, Peter Gutmann posted a security analysis of tinc 1.0.1. He argues that the 32 bit sequence number used by tinc is not a good IV, that tinc's default length of 4 bytes for the MAC is too short, and he doesn't like tinc's use of RSA during authentication. We do not know of a security hole -in this version of tinc, but tinc's security is not as strong as TLS or IPsec. -We will address these issues in tinc 2.0. +in the legacy protocol of tinc, but it is not as strong as TLS or IPsec. + +The Sweet32 attack affects versions of tinc prior to 1.0.30. + +On September 6th, 2018, Michael Yonly contacted us and provided +proof-of-concept code that allowed a remote attacker to create an +authenticated, one-way connection with a node, and also that there was a +possibility for a man-in-the-middle to force UDP packets from a node to be sent +in plaintext. The first issue was trivial to exploit on tinc versions prior to +1.0.30, but the changes in 1.0.30 to mitigate the Sweet32 attack made this +weakness much harder to exploit. These issues have been fixed in tinc 1.0.35. + +This version of tinc comes with an improved protocol, called Simple +Peer-to-Peer Security (SPTPS), which aims to be as strong as TLS with one of +the strongest cipher suites. None of the above security issues affected SPTPS. +However, be aware that SPTPS is only used between nodes running tinc 1.1pre* or +later, and in a VPN with nodes running different versions, the security might +only be as good as that of the oldest version. Cryptography is a hard thing to get right. We cannot make any guarantees. Time, review and feedback are the only things that can @@ -2460,44 +3411,44 @@ netmask should be such that it encompasses the entire VPN. For IPv4 addresses: -@multitable {Darwin (Mac OS X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @item Linux -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} @item Linux iproute2 -@tab @code{ip addr add} @var{address}@code{/}@var{prefixlength} @code{dev} @var{interface} +@tab @command{ip addr add} @var{address}@samp{/}@var{prefixlength} @samp{dev} @var{interface} @item FreeBSD -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} @item OpenBSD -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} @item NetBSD -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} @item Solaris -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} -@item Darwin (Mac OS X) -@tab @code{ifconfig} @var{interface} @var{address} @code{netmask} @var{netmask} +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} +@item Darwin (MacOS/X) +@tab @command{ifconfig} @var{interface} @var{address} @samp{netmask} @var{netmask} @item Windows -@tab @code{netsh interface ip set address} @var{interface} @code{static} @var{address} @var{netmask} +@tab @command{netsh interface ip set address} @var{interface} @samp{static} @var{address} @var{netmask} @end multitable For IPv6 addresses: -@multitable {Darwin (Mac OS X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @item Linux -@tab @code{ifconfig} @var{interface} @code{add} @var{address}@code{/}@var{prefixlength} +@tab @command{ifconfig} @var{interface} @samp{add} @var{address}@samp{/}@var{prefixlength} @item FreeBSD -@tab @code{ifconfig} @var{interface} @code{inet6} @var{address} @code{prefixlen} @var{prefixlength} +@tab @command{ifconfig} @var{interface} @samp{inet6} @var{address} @samp{prefixlen} @var{prefixlength} @item OpenBSD -@tab @code{ifconfig} @var{interface} @code{inet6} @var{address} @code{prefixlen} @var{prefixlength} +@tab @command{ifconfig} @var{interface} @samp{inet6} @var{address} @samp{prefixlen} @var{prefixlength} @item NetBSD -@tab @code{ifconfig} @var{interface} @code{inet6} @var{address} @code{prefixlen} @var{prefixlength} +@tab @command{ifconfig} @var{interface} @samp{inet6} @var{address} @samp{prefixlen} @var{prefixlength} @item Solaris -@tab @code{ifconfig} @var{interface} @code{inet6 plumb up} +@tab @command{ifconfig} @var{interface} @samp{inet6 plumb up} @item -@tab @code{ifconfig} @var{interface} @code{inet6 addif} @var{address} @var{address} -@item Darwin (Mac OS X) -@tab @code{ifconfig} @var{interface} @code{inet6} @var{address} @code{prefixlen} @var{prefixlength} +@tab @command{ifconfig} @var{interface} @samp{inet6 addif} @var{address} @var{address} +@item Darwin (MacOS/X) +@tab @command{ifconfig} @var{interface} @samp{inet6} @var{address} @samp{prefixlen} @var{prefixlength} @item Windows -@tab @code{netsh interface ipv6 add address} @var{interface} @code{static} @var{address}/@var{prefixlength} +@tab @command{netsh interface ipv6 add address} @var{interface} @samp{static} @var{address}/@var{prefixlength} @end multitable On Linux, it is possible to create a persistent tun/tap interface which will @@ -2505,9 +3456,9 @@ continue to exist even if tinc quit, although this is normally not required. It can be useful to set up a tun/tap interface owned by a non-root user, so tinc can be started without needing any root privileges at all. -@multitable {Darwin (Mac OS X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @item Linux -@tab @code{ip tuntap add dev} @var{interface} @code{mode} @var{tun|tap} @code{user} @var{username} +@tab @command{ip tuntap add dev} @var{interface} @samp{mode} @var{tun|tap} @samp{user} @var{username} @end multitable @c ================================================================== @@ -2523,44 +3474,44 @@ support this. Adding routes to IPv4 subnets: -@multitable {Darwin (Mac OS X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @item Linux -@tab @code{route add -net} @var{network_address} @code{netmask} @var{netmask} @var{interface} +@tab @command{route add -net} @var{network_address} @samp{netmask} @var{netmask} @var{interface} @item Linux iproute2 -@tab @code{ip route add} @var{network_address}@code{/}@var{prefixlength} @code{dev} @var{interface} +@tab @command{ip route add} @var{network_address}@samp{/}@var{prefixlength} @samp{dev} @var{interface} @item FreeBSD -@tab @code{route add} @var{network_address}@code{/}@var{prefixlength} @var{local_address} +@tab @command{route add} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @item OpenBSD -@tab @code{route add} @var{network_address}@code{/}@var{prefixlength} @var{local_address} +@tab @command{route add} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @item NetBSD -@tab @code{route add} @var{network_address}@code{/}@var{prefixlength} @var{local_address} +@tab @command{route add} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @item Solaris -@tab @code{route add} @var{network_address}@code{/}@var{prefixlength} @var{local_address} @code{-interface} -@item Darwin (Mac OS X) -@tab @code{route add} @var{network_address}@code{/}@var{prefixlength} @code{-interface} @var{interface} +@tab @command{route add} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @samp{-interface} +@item Darwin (MacOS/X) +@tab @command{route add} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @item Windows -@tab @code{netsh routing ip add persistentroute} @var{network_address} @var{netmask} @var{interface} @var{local_address} +@tab @command{netsh routing ip add persistentroute} @var{network_address} @var{netmask} @var{interface} @var{local_address} @end multitable Adding routes to IPv6 subnets: -@multitable {Darwin (Mac OS X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @item Linux -@tab @code{route add -A inet6} @var{network_address}@code{/}@var{prefixlength} @var{interface} +@tab @command{route add -A inet6} @var{network_address}@samp{/}@var{prefixlength} @var{interface} @item Linux iproute2 -@tab @code{ip route add} @var{network_address}@code{/}@var{prefixlength} @code{dev} @var{interface} +@tab @command{ip route add} @var{network_address}@samp{/}@var{prefixlength} @samp{dev} @var{interface} @item FreeBSD -@tab @code{route add -inet6} @var{network_address}@code{/}@var{prefixlength} @var{local_address} +@tab @command{route add -inet6} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @item OpenBSD -@tab @code{route add -inet6} @var{network_address} @var{local_address} @code{-prefixlen} @var{prefixlength} +@tab @command{route add -inet6} @var{network_address} @var{local_address} @samp{-prefixlen} @var{prefixlength} @item NetBSD -@tab @code{route add -inet6} @var{network_address} @var{local_address} @code{-prefixlen} @var{prefixlength} +@tab @command{route add -inet6} @var{network_address} @var{local_address} @samp{-prefixlen} @var{prefixlength} @item Solaris -@tab @code{route add -inet6} @var{network_address}@code{/}@var{prefixlength} @var{local_address} @code{-interface} -@item Darwin (Mac OS X) -@tab @code{route add -inet6} @var{network_address}@code{/}@var{prefixlength} @code{-interface} @var{interface} +@tab @command{route add -inet6} @var{network_address}@samp{/}@var{prefixlength} @var{local_address} @samp{-interface} +@item Darwin (MacOS/X) +@tab ? @item Windows -@tab @code{netsh interface ipv6 add route} @var{network address}/@var{prefixlength} @var{interface} +@tab @command{netsh interface ipv6 add route} @var{network address}/@var{prefixlength} @var{interface} @end multitable @c ================================================================== @@ -2582,10 +3533,10 @@ There are many Linux distributions, and historically, many of them had their own way of starting programs at boot time. Today, a number of major Linux distributions have chosen to use systemd as their init system. Tinc ships with systemd service files that allow you to start and stop tinc using systemd. -There are two service files: @code{tinc.service} is used to globally enable or +There are two service files: @samp{tinc.service} is used to globally enable or disable all tinc daemons managed by systemd, and -@code{tinc@@@var{netname}.service} is used to enable or disable specific tinc -daemons. So if one has created a tinc network with netname @code{foo}, then +@samp{tinc@@@var{netname}.service} is used to enable or disable specific tinc +daemons. So if one has created a tinc network with netname @samp{foo}, then you have to run the following two commands to ensure it is started at boot time: @@ -2601,7 +3552,7 @@ following command: systemctl start tinc@@foo @end example -You can also use @samp{systemctl start tinc}, this will start all tinc daemons +You can also use @command{systemctl start tinc}, this will start all tinc daemons that are enabled. You can stop and disable tinc networks in the same way. If your system is not using systemd, then you have to look up your @@ -2611,11 +3562,12 @@ distribution's way of starting tinc at boot time. @node Windows @subsection Windows -On Windows, if tinc is started without the @code{-D} or @code{--no-detach} -option, it will automatically register itself as a service that is started at -boot time. When tinc is stopped using the @code{-k} or @code{--kill}, it will -also automatically unregister itself. Once tinc is registered as a service, it -is also possible to stop and start tinc using the Windows Services Manager. +On Windows, if tinc is started with the @command{tinc start} command without using +the @option{-D} or @option{--no-detach} option, it will automatically register +itself as a service that is started at boot time. When tinc is stopped using +the @command{tinc stop} command, it will also automatically unregister itself. +Once tinc is registered as a service, it is also possible to stop and start +tinc using the Windows Services Manager. @c ================================================================== @node Other platforms diff --git a/doc/tincd.8.in b/doc/tincd.8.in index bdccf9d..4c2348e 100644 --- a/doc/tincd.8.in +++ b/doc/tincd.8.in @@ -1,4 +1,4 @@ -.Dd 2014-05-11 +.Dd 2013-01-14 .Dt TINCD 8 .\" Manual page created by: .\" Ivo Timmermans @@ -8,17 +8,15 @@ .Nd tinc VPN daemon .Sh SYNOPSIS .Nm -.Op Fl cdDkKnoLRU +.Op Fl cdDKnsoLRU .Op Fl -config Ns = Ns Ar DIR .Op Fl -no-detach .Op Fl -debug Ns Op = Ns Ar LEVEL -.Op Fl -kill Ns Op = Ns Ar SIGNAL .Op Fl -net Ns = Ns Ar NETNAME -.Op Fl -generate-keys Ns Op = Ns Ar BITS .Op Fl -option Ns = Ns Ar [HOST.]KEY=VALUE .Op Fl -mlock .Op Fl -logfile Ns Op = Ns Ar FILE -.Op Fl -pidfile Ns = Ns Ar FILE +.Op Fl -syslog .Op Fl -bypass-security .Op Fl -chroot .Op Fl -user Ns = Ns Ar USER @@ -37,7 +35,7 @@ If that succeeds, it will detach from the controlling terminal and continue in the background, accepting and setting up connections to other tinc daemons that are part of the virtual private network. -Under Windows (not Cygwin) tinc will install itself as a service, +Under Windows tinc will install itself as a service, which will be restarted automatically after reboots. .Sh OPTIONS .Bl -tag -width indent @@ -54,14 +52,6 @@ If not mentioned otherwise, this will show log messages on the standard error ou Increase debug level or set it to .Ar LEVEL (see below). -.It Fl k, -kill Ns Op = Ns Ar SIGNAL -Attempt to kill a running -.Nm -(optionally with the specified -.Ar SIGNAL -instead of SIGTERM) and exit. -Under Windows (not Cygwin) the optional argument is ignored, -the service will always be stopped and removed. .It Fl n, -net Ns = Ns Ar NETNAME Connect to net .Ar NETNAME . @@ -73,13 +63,6 @@ for .Ar NETNAME is the same as not specifying any .Ar NETNAME . -.It Fl K, -generate-keys Ns Op = Ns Ar BITS -Generate public/private RSA keypair and exit. -If -.Ar BITS -is omitted, the default length will be 2048 bits. -When saving keys to existing files, tinc will not delete the old keys, -you have to remove them manually. .It Fl o, -option Ns = Ns Ar [HOST.]KEY=VALUE Without specifying a .Ar HOST , @@ -99,18 +82,25 @@ This option can be used more than once to specify multiple configuration variabl .It Fl L, -mlock Lock tinc into main memory. This will prevent sensitive data like shared private keys to be written to the system swap files/partitions. +This option is not supported on all platforms. .It Fl -logfile Ns Op = Ns Ar FILE Write log entries to a file instead of to the system logging facility. If .Ar FILE is omitted, the default is .Pa @localstatedir@/log/tinc. Ns Ar NETNAME Ns Pa .log. -.It Fl -pidfile Ns = Ns Ar FILE -Write PID to +.It Fl s, -syslog +When this option is is set, tinc uses syslog instead of stderr in --no-detach mode. +.It Fl -pidfile Ns = Ns Ar FILENAME +Store a cookie in +.Ar FILENAME +which allows +.Xr tinc 8 +to authenticate. +If .Ar FILE -instead of +is omitted, the default is .Pa @runstatedir@/tinc. Ns Ar NETNAME Ns Pa .pid. -Under Windows this option will be ignored. .It Fl -bypass-security Disables encryption and authentication of the meta protocol. Only useful for debugging. @@ -118,10 +108,12 @@ Only useful for debugging. With this option tinc chroots into the directory where network config is located (@sysconfdir@/tinc/NETNAME if -n option is used, or to the directory specified with -c option) after initialization. +This option is not supported on all platforms. .It Fl U, -user Ns = Ns Ar USER setuid to the specified .Ar USER after initialization. +This option is not supported on all platforms. .It Fl -help Display short list of options. .It Fl -version @@ -151,15 +143,6 @@ If the .Fl -logfile option is used, this will also close and reopen the log file, useful when log rotation is used. -.It INT -Temporarily increases debug level to 5. -Send this signal again to revert to the original level. -.It USR1 -Dumps the connection list to syslog. -.It USR2 -Dumps virtual network device statistics, all known nodes, edges and subnets to syslog. -.It WINCH -Purges all information remembered about unreachable nodes. .El .Sh DEBUG LEVELS The tinc daemon can send a lot of messages to the syslog. @@ -206,6 +189,7 @@ If you find any bugs, report them to tinc@tinc-vpn.org. .Sh TODO A lot, especially security auditing. .Sh SEE ALSO +.Xr tinc 8 , .Xr tinc.conf 5 , .Pa https://www.tinc-vpn.org/ , .Pa http://www.cabal.org/ . diff --git a/doc/tincinclude.texi b/doc/tincinclude.texi index ab2a41a..51fd309 100644 --- a/doc/tincinclude.texi +++ b/doc/tincinclude.texi @@ -1,5 +1,5 @@ -@set VERSION 1.0.36 +@set VERSION 1.1pre17-49-g4cc4b9bc @set PACKAGE tinc -@set sysconfdir /etc -@set localstatedir /var -@set runstatedir /run +@set sysconfdir /usr/local/etc +@set localstatedir /usr/local/var +@set runstatedir /usr/local/var/run diff --git a/install-sh b/install-sh index 20d8b2e..ec298b5 100755 --- a/install-sh +++ b/install-sh @@ -1,7 +1,7 @@ #!/bin/sh # install - install a program, script, or datafile -scriptversion=2018-03-11.20; # UTC +scriptversion=2020-11-14.01; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the @@ -69,6 +69,11 @@ posix_mkdir= # Desired mode of installed file. mode=0755 +# Create dirs (including intermediate dirs) using mode 755. +# This is like GNU 'install' as of coreutils 8.32 (2020). +mkdir_umask=22 + +backupsuffix= chgrpcmd= chmodcmd=$chmodprog chowncmd= @@ -99,18 +104,28 @@ Options: --version display version info and exit. -c (ignored) - -C install only if different (preserve the last data modification time) + -C install only if different (preserve data modification time) -d create directories instead of installing files. -g GROUP $chgrpprog installed files to GROUP. -m MODE $chmodprog installed files to MODE. -o USER $chownprog installed files to USER. + -p pass -p to $cpprog. -s $stripprog installed files. + -S SUFFIX attempt to back up existing files, with suffix SUFFIX. -t DIRECTORY install into DIRECTORY. -T report an error if DSTFILE is a directory. Environment variables override the default commands: CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG + +By default, rm is invoked with -f; when overridden with RMPROG, +it's up to you to specify -f if you want it. + +If -S is not specified, no backups are attempted. + +Email bug reports to bug-automake@gnu.org. +Automake home page: https://www.gnu.org/software/automake/ " while test $# -ne 0; do @@ -137,8 +152,13 @@ while test $# -ne 0; do -o) chowncmd="$chownprog $2" shift;; + -p) cpprog="$cpprog -p";; + -s) stripcmd=$stripprog;; + -S) backupsuffix="$2" + shift;; + -t) is_target_a_directory=always dst_arg=$2 @@ -255,6 +275,10 @@ do dstdir=$dst test -d "$dstdir" dstdir_status=$? + # Don't chown directories that already exist. + if test $dstdir_status = 0; then + chowncmd="" + fi else # Waiting for this to be detected by the "$cpprog $src $dsttmp" command @@ -301,22 +325,6 @@ do if test $dstdir_status != 0; then case $posix_mkdir in '') - # Create intermediate dirs using mode 755 as modified by the umask. - # This is like FreeBSD 'install' as of 1997-10-28. - umask=`umask` - case $stripcmd.$umask in - # Optimize common cases. - *[2367][2367]) mkdir_umask=$umask;; - .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; - - *[0-7]) - mkdir_umask=`expr $umask + 22 \ - - $umask % 100 % 40 + $umask % 20 \ - - $umask % 10 % 4 + $umask % 2 - `;; - *) mkdir_umask=$umask,go-w;; - esac - # With -d, create the new directory with the user-specified mode. # Otherwise, rely on $mkdir_umask. if test -n "$dir_arg"; then @@ -326,52 +334,49 @@ do fi posix_mkdir=false - case $umask in - *[123567][0-7][0-7]) - # POSIX mkdir -p sets u+wx bits regardless of umask, which - # is incompatible with FreeBSD 'install' when (umask & 300) != 0. - ;; - *) - # Note that $RANDOM variable is not portable (e.g. dash); Use it - # here however when possible just to lower collision chance. - tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + # The $RANDOM variable is not portable (e.g., dash). Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + trap ' + ret=$? + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null + exit $ret + ' 0 - # Because "mkdir -p" follows existing symlinks and we likely work - # directly in world-writeable /tmp, make sure that the '$tmpdir' - # directory is successfully created first before we actually test - # 'mkdir -p' feature. - if (umask $mkdir_umask && - $mkdirprog $mkdir_mode "$tmpdir" && - exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 - then - if test -z "$dir_arg" || { - # Check for POSIX incompatibilities with -m. - # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or - # other-writable bit of parent directory when it shouldn't. - # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. - test_tmpdir="$tmpdir/a" - ls_ld_tmpdir=`ls -ld "$test_tmpdir"` - case $ls_ld_tmpdir in - d????-?r-*) different_mode=700;; - d????-?--*) different_mode=755;; - *) false;; - esac && - $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { - ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` - test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" - } - } - then posix_mkdir=: - fi - rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" - else - # Remove any dirs left behind by ancient mkdir implementations. - rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null - fi - trap '' 0;; - esac;; + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p'. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; esac if @@ -382,7 +387,7 @@ do then : else - # The umask is ridiculous, or mkdir does not conform to POSIX, + # mkdir does not conform to POSIX, # or it failed possibly due to a race condition. Create the # directory the slow way, step by step, checking for races as we go. @@ -411,7 +416,7 @@ do prefixes= else if $posix_mkdir; then - (umask=$mkdir_umask && + (umask $mkdir_umask && $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break # Don't fail if two instances are running concurrently. test -d "$prefix" || exit 1 @@ -488,6 +493,13 @@ do then rm -f "$dsttmp" else + # If $backupsuffix is set, and the file being installed + # already exists, attempt a backup. Don't worry if it fails, + # e.g., if mv doesn't support -f. + if test -n "$backupsuffix" && test -f "$dst"; then + $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null + fi + # Rename the file to the real destination. $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || @@ -502,9 +514,9 @@ do # file should still install successfully. { test ! -f "$dst" || - $doit $rmcmd -f "$dst" 2>/dev/null || + $doit $rmcmd "$dst" 2>/dev/null || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && - { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } } || { echo "$0: cannot unlink or rename $dst" >&2 (exit 1); exit 1 diff --git a/m4/attribute.m4 b/m4/attribute.m4 index 9d673e9..3228deb 100644 --- a/m4/attribute.m4 +++ b/m4/attribute.m4 @@ -9,8 +9,8 @@ AC_DEFUN([tinc_ATTRIBUTE], CFLAGS="$CFLAGS -Wall -Werror" AC_COMPILE_IFELSE( [AC_LANG_SOURCE( - [void *test(void) __attribute__ (($1)); - void *test(void) { return (void *)0; } + [void *test(void *x) __attribute__ (($1)); + void *test(void *x) { return (void *)x; } ], )], [tinc_cv_attribute_$1=yes], diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4 index 1d38b76..08f2e07 100644 --- a/m4/ax_append_flag.m4 +++ b/m4/ax_append_flag.m4 @@ -49,21 +49,23 @@ # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. -#serial 2 +#serial 6 AC_DEFUN([AX_APPEND_FLAG], -[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX -AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])dnl -AS_VAR_SET_IF(FLAGS, - [case " AS_VAR_GET(FLAGS) " in - *" $1 "*) - AC_RUN_LOG([: FLAGS already contains $1]) - ;; - *) - AC_RUN_LOG([: FLAGS="$FLAGS $1"]) - AS_VAR_SET(FLAGS, ["AS_VAR_GET(FLAGS) $1"]) - ;; - esac], - [AS_VAR_SET(FLAGS,["$1"])]) +[dnl +AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) +AS_VAR_SET_IF(FLAGS,[ + AS_CASE([" AS_VAR_GET(FLAGS) "], + [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], + [ + AS_VAR_APPEND(FLAGS,[" $1"]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) + ], + [ + AS_VAR_SET(FLAGS,[$1]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) AS_VAR_POPDEF([FLAGS])dnl ])dnl AX_APPEND_FLAG diff --git a/m4/ax_code_coverage.m4 b/m4/ax_code_coverage.m4 new file mode 100644 index 0000000..0934a44 --- /dev/null +++ b/m4/ax_code_coverage.m4 @@ -0,0 +1,264 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CODE_COVERAGE() +# +# DESCRIPTION +# +# Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS, +# CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included +# in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every +# build target (program or library) which should be built with code +# coverage support. Also defines CODE_COVERAGE_RULES which should be +# substituted in your Makefile; and $enable_code_coverage which can be +# used in subsequent configure output. CODE_COVERAGE_ENABLED is defined +# and substituted, and corresponds to the value of the +# --enable-code-coverage option, which defaults to being disabled. +# +# Test also for gcov program and create GCOV variable that could be +# substituted. +# +# Note that all optimisation flags in CFLAGS must be disabled when code +# coverage is enabled. +# +# Usage example: +# +# configure.ac: +# +# AX_CODE_COVERAGE +# +# Makefile.am: +# +# @CODE_COVERAGE_RULES@ +# my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ... +# my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ... +# my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ... +# my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ... +# +# This results in a "check-code-coverage" rule being added to any +# Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module +# has been configured with --enable-code-coverage). Running `make +# check-code-coverage` in that directory will run the module's test suite +# (`make check`) and build a code coverage report detailing the code which +# was touched, then print the URI for the report. +# +# In earlier versions of this macro, CODE_COVERAGE_LDFLAGS was defined +# instead of CODE_COVERAGE_LIBS. They are both still defined, but use of +# CODE_COVERAGE_LIBS is preferred for clarity; CODE_COVERAGE_LDFLAGS is +# deprecated. They have the same value. +# +# This code was derived from Makefile.decl in GLib, originally licenced +# under LGPLv2.1+. +# +# LICENSE +# +# Copyright (c) 2012, 2016 Philip Withnall +# Copyright (c) 2012 Xan Lopez +# Copyright (c) 2012 Christian Persch +# Copyright (c) 2012 Paolo Borelli +# Copyright (c) 2012 Dan Winship +# Copyright (c) 2015 Bastien ROUCARIES +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# +# This library 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 Lesser +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . + +#serial 21 + +AC_DEFUN([AX_CODE_COVERAGE],[ + dnl Check for --enable-code-coverage + AC_REQUIRE([AC_PROG_SED]) + + # allow to override gcov location + AC_ARG_WITH([gcov], + [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])], + [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov], + [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov]) + + AC_MSG_CHECKING([whether to build with code coverage support]) + AC_ARG_ENABLE([code-coverage], + AS_HELP_STRING([--enable-code-coverage], + [Whether to enable code coverage support]),, + enable_code_coverage=no) + + AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes]) + AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage]) + AC_MSG_RESULT($enable_code_coverage) + + AS_IF([ test "$enable_code_coverage" = "yes" ], [ + # check for gcov + AC_CHECK_TOOL([GCOV], + [$_AX_CODE_COVERAGE_GCOV_PROG_WITH], + [:]) + AS_IF([test "X$GCOV" = "X:"], + [AC_MSG_ERROR([gcov is needed to do coverage])]) + AC_SUBST([GCOV]) + + dnl Check if gcc is being used + AS_IF([ test "$GCC" = "no" ], [ + AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage]) + ]) + + AC_CHECK_PROG([LCOV], [lcov], [lcov]) + AC_CHECK_PROG([GENHTML], [genhtml], [genhtml]) + + AS_IF([ test -z "$LCOV" ], [ + AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed]) + ]) + + AS_IF([ test -z "$GENHTML" ], [ + AC_MSG_ERROR([Could not find genhtml from the lcov package]) + ]) + + dnl Build the code coverage flags + dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility + CODE_COVERAGE_CPPFLAGS="-DNDEBUG" + CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" + CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage" + CODE_COVERAGE_LIBS="-lgcov" + CODE_COVERAGE_LDFLAGS="$CODE_COVERAGE_LIBS" + + AC_SUBST([CODE_COVERAGE_CPPFLAGS]) + AC_SUBST([CODE_COVERAGE_CFLAGS]) + AC_SUBST([CODE_COVERAGE_CXXFLAGS]) + AC_SUBST([CODE_COVERAGE_LIBS]) + AC_SUBST([CODE_COVERAGE_LDFLAGS]) + + [CODE_COVERAGE_RULES_CHECK=' + -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check + $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture +'] + [CODE_COVERAGE_RULES_CAPTURE=' + $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS) + $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS) + -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp + $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS) + @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html" +'] + [CODE_COVERAGE_RULES_CLEAN=' +clean: code-coverage-clean +distclean: code-coverage-clean +code-coverage-clean: + -$(LCOV) --directory $(top_builddir) -z + -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY) + -find . \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete +'] + ], [ + [CODE_COVERAGE_RULES_CHECK=' + @echo "Need to reconfigure with --enable-code-coverage" +'] + CODE_COVERAGE_RULES_CAPTURE="$CODE_COVERAGE_RULES_CHECK" + CODE_COVERAGE_RULES_CLEAN='' + ]) + +[CODE_COVERAGE_RULES=' +# Code coverage +# +# Optional: +# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting. +# Multiple directories may be specified, separated by whitespace. +# (Default: $(top_builddir)) +# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated +# by lcov for code coverage. (Default: +# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info) +# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage +# reports to be created. (Default: +# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage) +# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage, +# set to 0 to disable it and leave empty to stay with the default. +# (Default: empty) +# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov +# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) +# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov +# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) +# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov +# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the +# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) +# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov +# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) +# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering +# lcov instance. (Default: empty) +# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov +# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) +# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the +# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE) +# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml +# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT) +# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore +# +# The generated report will be titled using the $(PACKAGE_NAME) and +# $(PACKAGE_VERSION). In order to add the current git hash to the title, +# use the git-version-gen script, available online. + +# Optional variables +CODE_COVERAGE_DIRECTORY ?= $(top_builddir) +CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info +CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage +CODE_COVERAGE_BRANCH_COVERAGE ?= +CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ +--rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) +CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT) +CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)" +CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH) +CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) +CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?= +CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT) +CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\ +$(if $(CODE_COVERAGE_BRANCH_COVERAGE),\ +--rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE)) +CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS) +CODE_COVERAGE_IGNORE_PATTERN ?= + +code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V)) +code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\ + $(CODE_COVERAGE_OUTPUT_FILE); +code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V)) +code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\ + $(CODE_COVERAGE_IGNORE_PATTERN); +code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V)) +code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY)) +code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY); +code_coverage_quiet = $(code_coverage_quiet_$(V)) +code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY)) +code_coverage_quiet_0 = --quiet + +# sanitizes the test-name: replaces with underscores: dashes and dots +code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1))) + +# Use recursive makes in order to ignore errors during check +check-code-coverage:'"$CODE_COVERAGE_RULES_CHECK"' + +# Capture code coverage data +code-coverage-capture: code-coverage-capture-hook'"$CODE_COVERAGE_RULES_CAPTURE"' + +# Hook rule executed before code-coverage-capture, overridable by the user +code-coverage-capture-hook: + +'"$CODE_COVERAGE_RULES_CLEAN"' + +GITIGNOREFILES ?= +GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY) + +A''M_DISTCHECK_CONFIGURE_FLAGS ?= +A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage + +.PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean +'] + + AC_SUBST([CODE_COVERAGE_RULES]) + m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])]) +]) diff --git a/m4/curses.m4 b/m4/curses.m4 new file mode 100644 index 0000000..031e1ee --- /dev/null +++ b/m4/curses.m4 @@ -0,0 +1,44 @@ +dnl Check to find the curses headers/libraries + +AC_DEFUN([tinc_CURSES], +[ + AC_ARG_ENABLE([curses], + AS_HELP_STRING([--disable-curses], [disable curses support])) + AS_IF([test "x$enable_curses" != "xno"], [ + AC_DEFINE(HAVE_CURSES, 1, [have curses support]) + curses=true + AC_ARG_WITH(curses, + AS_HELP_STRING([--with-curses=DIR], [curses base directory, or:]), + [curses="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(curses-include, + AS_HELP_STRING([--with-curses-include=DIR], [curses headers directory]), + [curses_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(curses-lib, + AS_HELP_STRING([--with-curses-lib=DIR], [curses library directory]), + [curses_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS(curses.h, + [], + [AC_MSG_ERROR("curses header files not found."); break] + ) + + AC_CHECK_LIB(ncurses, initscr, + [CURSES_LIBS="-lncurses"; AC_CHECK_LIB(tinfo, wtimeout, [CURSES_LIBS+=" -ltinfo"], [])], + [AC_CHECK_LIB(curses, initscr, + [CURSES_LIBS="-lcurses"], + [AC_MSG_ERROR("curses libraries not found.")] + )] + ) + ]) + + AC_SUBST(CURSES_LIBS) +]) diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4 new file mode 100644 index 0000000..01c7478 --- /dev/null +++ b/m4/libgcrypt.m4 @@ -0,0 +1,33 @@ +dnl Check to find the libgcrypt headers/libraries + +AC_DEFUN([tinc_LIBGCRYPT], +[ + AC_ARG_WITH(libgcrypt, + AS_HELP_STRING([--with-libgcrypt=DIR], [libgcrypt base directory, or:]), + [libgcrypt="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(libgcrypt-include, + AS_HELP_STRING([--with-libgcrypt-include=DIR], [libgcrypt headers directory (without trailing /libgcrypt)]), + [libgcrypt_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(libgcrypt-lib, + AS_HELP_STRING([--with-libgcrypt-lib=DIR], [libgcrypt library directory]), + [libgcrypt_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS([gcrypt.h], + [], + [AC_MSG_ERROR([libgcrypt header files not found.]); break] + ) + + AC_CHECK_LIB(gcrypt, gcry_cipher_encrypt, + [LIBS="-lgcrypt $LIBS"], + [AC_MSG_ERROR([libgcrypt libraries not found.])] + ) +]) diff --git a/m4/miniupnpc.m4 b/m4/miniupnpc.m4 new file mode 100644 index 0000000..c2aca29 --- /dev/null +++ b/m4/miniupnpc.m4 @@ -0,0 +1,40 @@ +dnl Check to find the miniupnpc headers/libraries + +AC_DEFUN([tinc_MINIUPNPC], +[ + AC_ARG_ENABLE([miniupnpc], + AS_HELP_STRING([--enable-miniupnpc], [enable miniupnpc support])) + AS_IF([test "x$enable_miniupnpc" = "xyes"], [ + AC_DEFINE(HAVE_MINIUPNPC, 1, [have miniupnpc support]) + AC_ARG_WITH(miniupnpc, + AS_HELP_STRING([--with-miniupnpc=DIR], [miniupnpc base directory, or:]), + [miniupnpc="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(miniupnpc-include, + AS_HELP_STRING([--with-miniupnpc-include=DIR], [miniupnpc headers directory]), + [miniupnpc_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(miniupnpc-lib, + AS_HELP_STRING([--with-miniupnpc-lib=DIR], [miniupnpc library directory]), + [miniupnpc_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS(miniupnpc/miniupnpc.h, + [], + [AC_MSG_ERROR("miniupnpc header files not found."); break] + ) + + AC_CHECK_LIB(miniupnpc, upnpDiscover, + [MINIUPNPC_LIBS="$LIBS -lminiupnpc"], + [AC_MSG_ERROR("miniupnpc libraries not found.")] + ) + ]) + + AC_SUBST(MINIUPNPC_LIBS) +]) diff --git a/m4/openssl.m4 b/m4/openssl.m4 index 895c31a..0ff939b 100644 --- a/m4/openssl.m4 +++ b/m4/openssl.m4 @@ -35,7 +35,7 @@ AC_DEFUN([tinc_OPENSSL], LDFLAGS="$LDFLAGS -L$withval"] ) - AC_CHECK_HEADERS(openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h, + AC_CHECK_HEADERS([openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h], [], [AC_MSG_ERROR([LibreSSL/OpenSSL header files not found.]); break] ) @@ -54,5 +54,6 @@ AC_DEFUN([tinc_OPENSSL], [#include ] ) - AC_CHECK_FUNCS([BN_GENCB_new RSA_set0_key], , , [#include ]) + AC_CHECK_FUNCS([BN_GENCB_new ERR_remove_state RSA_set0_key], , , [#include ]) + AC_CHECK_FUNCS([HMAC_CTX_new], , , [#include ]) ]) diff --git a/m4/readline.m4 b/m4/readline.m4 new file mode 100644 index 0000000..f29e692 --- /dev/null +++ b/m4/readline.m4 @@ -0,0 +1,42 @@ +dnl Check to find the readline headers/libraries + +AC_DEFUN([tinc_READLINE], +[ + AC_ARG_ENABLE([readline], + AS_HELP_STRING([--disable-readline], [disable readline support])) + AS_IF([test "x$enable_readline" != "xno"], [ + AC_DEFINE(HAVE_READLINE, 1, [have readline support]) + readline=true + AC_ARG_WITH(readline, + AS_HELP_STRING([--with-readline=DIR], [readline base directory, or:]), + [readline="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(readline-include, + AS_HELP_STRING([--with-readline-include=DIR], [readline headers directory]), + [readline_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(readline-lib, + AS_HELP_STRING([--with-readline-lib=DIR], [readline library directory]), + [readline_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS([readline/readline.h readline/history.h], + [], + [AC_MSG_ERROR("readline header files not found."); break] + ) + + AC_CHECK_LIB(readline, readline, + [READLINE_LIBS="-lreadline"], + [AC_MSG_ERROR("readline library not found.")], + [$CURSES_LIBS] + ) + ]) + + AC_SUBST(READLINE_LIBS) +]) diff --git a/src/Makefile.am b/src/Makefile.am index 7b3dd97..12261ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,54 +1,174 @@ ## Produce this file with automake to get Makefile.in -sbin_PROGRAMS = tincd +sbin_PROGRAMS = tincd tinc +check_PROGRAMS = sptps_test sptps_keypair +EXTRA_PROGRAMS = sptps_test sptps_keypair + +CLEANFILES = version_git.h + +.PHONY: version-stamp +version-stamp: + +version_git.h: version-stamp + $(AM_V_GEN)echo >$@ + @-(cd $(srcdir) && git describe 2>/dev/null >/dev/null) && echo '#define GIT_DESCRIPTION "'`(cd $(srcdir) && git describe) | sed 's/release-//'`'"' >$@ ||: +${srcdir}/version.c: version_git.h + +## Now a hack to appease some versions of BSD make that don't understand that "./foo" is the same as "foo". +if BSD +version.c: ${srcdir}/version.c +endif + +if LINUX +EXTRA_PROGRAMS += sptps_speed +endif + +ed25519_SOURCES = \ + ed25519/ed25519.h \ + ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h \ + ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c \ + ed25519/keypair.c \ + ed25519/precomp_data.h \ + ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h \ + ed25519/sign.c \ + ed25519/verify.c + +chacha_poly1305_SOURCES = \ + chacha-poly1305/chacha.c chacha-poly1305/chacha.h \ + chacha-poly1305/chacha-poly1305.c chacha-poly1305/chacha-poly1305.h \ + chacha-poly1305/poly1305.c chacha-poly1305/poly1305.h tincd_SOURCES = \ - have.h \ - system.h \ - avl_tree.c avl_tree.h \ + address_cache.c address_cache.h \ + autoconnect.c autoconnect.h \ + buffer.c buffer.h \ + cipher.h \ conf.c conf.h \ connection.c connection.h \ + control.c control.h \ + control_common.h \ + crypto.h \ device.h \ + digest.h \ dropin.c dropin.h \ dummy_device.c \ + ecdh.h \ + ecdsa.h \ + ecdsagen.h \ edge.c edge.h \ ethernet.h \ event.c event.h \ - fake-getaddrinfo.c fake-getaddrinfo.h \ - fake-getnameinfo.c fake-getnameinfo.h \ + fd_device.c \ graph.c graph.h \ + hash.c hash.h \ + have.h \ ipv4.h \ ipv6.h \ list.c list.h \ logger.c logger.h \ meta.c meta.h \ multicast_device.c \ + names.c names.h \ net.c net.h \ net_packet.c \ net_setup.c \ net_socket.c \ netutl.c netutl.h \ node.c node.h \ - pidfile.c pidfile.h \ + prf.h \ process.c process.h \ protocol.c protocol.h \ protocol_auth.c \ protocol_edge.c \ - protocol_misc.c \ protocol_key.c \ + protocol_misc.c \ protocol_subnet.c \ - proxy.c proxy.h \ raw_socket_device.c \ route.c route.h \ + rsa.h \ + rsagen.h \ + script.c script.h \ + splay_tree.c splay_tree.h \ + sptps.c sptps.h \ subnet.c subnet.h \ + subnet_parse.c \ + system.h \ tincd.c \ utils.c utils.h \ - xalloc.h + xalloc.h \ + version.c version.h \ + ed25519/ecdh.c \ + ed25519/ecdsa.c \ + $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) + +tinc_SOURCES = \ + dropin.c dropin.h \ + fsck.c fsck.h \ + ifconfig.c ifconfig.h \ + info.c info.h \ + invitation.c invitation.h \ + list.c list.h \ + names.c names.h \ + netutl.c netutl.h \ + script.c script.h \ + sptps.c sptps.h \ + subnet_parse.c subnet.h \ + tincctl.c tincctl.h \ + top.c top.h \ + utils.c utils.h \ + version.c version.h \ + ed25519/ecdh.c \ + ed25519/ecdsa.c \ + ed25519/ecdsagen.c \ + $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) + +sptps_test_SOURCES = \ + logger.c logger.h \ + sptps.c sptps.h \ + sptps_test.c \ + utils.c utils.h \ + ed25519/ecdh.c \ + ed25519/ecdsa.c \ + $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) + +sptps_keypair_SOURCES = \ + sptps_keypair.c \ + utils.c utils.h \ + ed25519/ecdsagen.c \ + $(ed25519_SOURCES) + +sptps_speed_SOURCES = \ + logger.c logger.h \ + sptps.c sptps.h \ + sptps_speed.c \ + utils.c utils.h \ + ed25519/ecdh.c \ + ed25519/ecdsa.c \ + ed25519/ecdsagen.c \ + $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) + +## Conditionally compile device drivers if !GETOPT tincd_SOURCES += \ getopt.c getopt.h \ getopt1.c +tinc_SOURCES += \ + getopt.c getopt.h \ + getopt1.c +sptps_test_SOURCES += \ + getopt.c getopt.h \ + getopt1.c +sptps_keypair_SOURCES += \ + getopt.c getopt.h \ + getopt1.c endif if LINUX @@ -70,10 +190,6 @@ if MINGW tincd_SOURCES += mingw/device.c mingw/common.h endif -if CYGWIN -tincd_SOURCES += cygwin/device.c -endif - if UML tincd_SOURCES += uml_device.c endif @@ -82,8 +198,88 @@ if VDE tincd_SOURCES += vde_device.c endif +if OPENSSL +tincd_SOURCES += \ + openssl/cipher.c \ + openssl/crypto.c \ + openssl/digest.c openssl/digest.h \ + openssl/prf.c \ + openssl/rsa.c +tinc_SOURCES += \ + openssl/cipher.c \ + openssl/crypto.c \ + openssl/digest.c openssl/digest.h \ + openssl/prf.c \ + openssl/rsa.c \ + openssl/rsagen.c +sptps_test_SOURCES += \ + openssl/crypto.c \ + openssl/digest.c openssl/digest.h \ + openssl/prf.c +sptps_keypair_SOURCES += \ + openssl/crypto.c +sptps_speed_SOURCES += \ + openssl/crypto.c \ + openssl/digest.c openssl/digest.h \ + openssl/prf.c +else +if GCRYPT +tincd_SOURCES += \ + gcrypt/cipher.c \ + gcrypt/crypto.c \ + gcrypt/digest.c gcrypt/digest.h \ + gcrypt/prf.c \ + gcrypt/rsa.c +tinc_SOURCES += \ + gcrypt/cipher.c \ + gcrypt/crypto.c \ + gcrypt/digest.c gcrypt/digest.h \ + gcrypt/prf.c \ + gcrypt/rsa.c \ + gcrypt/rsagen.c +sptps_test_SOURCES += \ + gcrypt/cipher.c \ + gcrypt/crypto.c \ + gcrypt/digest.c gcrypt/digest.h \ + gcrypt/prf.c +sptps_keypair_SOURCES += \ + openssl/crypto.c +sptps_speed_SOURCES += \ + openssl/crypto.c \ + openssl/digest.c openssl/digest.h \ + openssl/prf.c +else +tincd_SOURCES += \ + nolegacy/crypto.c \ + nolegacy/prf.c +tinc_SOURCES += \ + nolegacy/crypto.c \ + nolegacy/prf.c +sptps_test_SOURCES += \ + nolegacy/crypto.c \ + nolegacy/prf.c +sptps_keypair_SOURCES += \ + nolegacy/crypto.c +sptps_speed_SOURCES += \ + nolegacy/crypto.c \ + nolegacy/prf.c +endif +endif + +if MINIUPNPC +tincd_SOURCES += upnp.h upnp.c +tincd_LDADD = $(MINIUPNPC_LIBS) +tincd_LDFLAGS = -pthread +endif + +tinc_LDADD = $(READLINE_LIBS) $(CURSES_LIBS) +sptps_speed_LDADD = -lrt + +LIBS = @LIBS@ -lm $(CODE_COVERAGE_LIBS) + if TUNEMU LIBS += -lpcap endif -AM_CPPFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DRUNSTATEDIR=\"$(runstatedir)\" -DLOCALSTATEDIR=\"$(localstatedir)\" -I $(abs_top_builddir)/ +AM_CFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DRUNSTATEDIR=\"$(runstatedir)\" -DLOCALSTATEDIR=\"$(localstatedir)\" -DSBINDIR=\"$(sbindir)\" -iquote. $(CODE_COVERAGE_CFLAGS) +AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS) diff --git a/src/Makefile.in b/src/Makefile.in index 5d52921..68a1e8a 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.2 from Makefile.am. +# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -88,20 +88,112 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -sbin_PROGRAMS = tincd$(EXEEXT) -@GETOPT_FALSE@am__append_1 = \ +sbin_PROGRAMS = tincd$(EXEEXT) tinc$(EXEEXT) +check_PROGRAMS = sptps_test$(EXEEXT) sptps_keypair$(EXEEXT) +EXTRA_PROGRAMS = sptps_test$(EXEEXT) sptps_keypair$(EXEEXT) \ + $(am__EXEEXT_1) +@LINUX_TRUE@am__append_1 = sptps_speed +@GETOPT_FALSE@am__append_2 = \ @GETOPT_FALSE@ getopt.c getopt.h \ @GETOPT_FALSE@ getopt1.c -@LINUX_TRUE@am__append_2 = linux/device.c -@BSD_TRUE@am__append_3 = bsd/device.c -@BSD_TRUE@@TUNEMU_TRUE@am__append_4 = bsd/tunemu.c bsd/tunemu.h -@SOLARIS_TRUE@am__append_5 = solaris/device.c -@MINGW_TRUE@am__append_6 = mingw/device.c mingw/common.h -@CYGWIN_TRUE@am__append_7 = cygwin/device.c -@UML_TRUE@am__append_8 = uml_device.c -@VDE_TRUE@am__append_9 = vde_device.c -@TUNEMU_TRUE@am__append_10 = -lpcap +@GETOPT_FALSE@am__append_3 = \ +@GETOPT_FALSE@ getopt.c getopt.h \ +@GETOPT_FALSE@ getopt1.c + +@GETOPT_FALSE@am__append_4 = \ +@GETOPT_FALSE@ getopt.c getopt.h \ +@GETOPT_FALSE@ getopt1.c + +@GETOPT_FALSE@am__append_5 = \ +@GETOPT_FALSE@ getopt.c getopt.h \ +@GETOPT_FALSE@ getopt1.c + +@LINUX_TRUE@am__append_6 = linux/device.c +@BSD_TRUE@am__append_7 = bsd/device.c +@BSD_TRUE@@TUNEMU_TRUE@am__append_8 = bsd/tunemu.c bsd/tunemu.h +@SOLARIS_TRUE@am__append_9 = solaris/device.c +@MINGW_TRUE@am__append_10 = mingw/device.c mingw/common.h +@UML_TRUE@am__append_11 = uml_device.c +@VDE_TRUE@am__append_12 = vde_device.c +@OPENSSL_TRUE@am__append_13 = \ +@OPENSSL_TRUE@ openssl/cipher.c \ +@OPENSSL_TRUE@ openssl/crypto.c \ +@OPENSSL_TRUE@ openssl/digest.c openssl/digest.h \ +@OPENSSL_TRUE@ openssl/prf.c \ +@OPENSSL_TRUE@ openssl/rsa.c + +@OPENSSL_TRUE@am__append_14 = \ +@OPENSSL_TRUE@ openssl/cipher.c \ +@OPENSSL_TRUE@ openssl/crypto.c \ +@OPENSSL_TRUE@ openssl/digest.c openssl/digest.h \ +@OPENSSL_TRUE@ openssl/prf.c \ +@OPENSSL_TRUE@ openssl/rsa.c \ +@OPENSSL_TRUE@ openssl/rsagen.c + +@OPENSSL_TRUE@am__append_15 = \ +@OPENSSL_TRUE@ openssl/crypto.c \ +@OPENSSL_TRUE@ openssl/digest.c openssl/digest.h \ +@OPENSSL_TRUE@ openssl/prf.c + +@OPENSSL_TRUE@am__append_16 = \ +@OPENSSL_TRUE@ openssl/crypto.c + +@OPENSSL_TRUE@am__append_17 = \ +@OPENSSL_TRUE@ openssl/crypto.c \ +@OPENSSL_TRUE@ openssl/digest.c openssl/digest.h \ +@OPENSSL_TRUE@ openssl/prf.c + +@GCRYPT_TRUE@@OPENSSL_FALSE@am__append_18 = \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/cipher.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.c gcrypt/digest.h \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsa.c + +@GCRYPT_TRUE@@OPENSSL_FALSE@am__append_19 = \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/cipher.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.c gcrypt/digest.h \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsa.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsagen.c + +@GCRYPT_TRUE@@OPENSSL_FALSE@am__append_20 = \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/cipher.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.c gcrypt/digest.h \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.c + +@GCRYPT_TRUE@@OPENSSL_FALSE@am__append_21 = \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/crypto.c + +@GCRYPT_TRUE@@OPENSSL_FALSE@am__append_22 = \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/crypto.c \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/digest.c openssl/digest.h \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/prf.c + +@GCRYPT_FALSE@@OPENSSL_FALSE@am__append_23 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.c \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/prf.c + +@GCRYPT_FALSE@@OPENSSL_FALSE@am__append_24 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.c \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/prf.c + +@GCRYPT_FALSE@@OPENSSL_FALSE@am__append_25 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.c \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/prf.c + +@GCRYPT_FALSE@@OPENSSL_FALSE@am__append_26 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.c + +@GCRYPT_FALSE@@OPENSSL_FALSE@am__append_27 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.c \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/prf.c + +@MINIUPNPC_TRUE@am__append_28 = upnp.h upnp.c +@TUNEMU_TRUE@am__append_29 = -lpcap subdir = src ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ @@ -109,9 +201,12 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ $(top_srcdir)/m4/ax_check_compile_flag.m4 \ $(top_srcdir)/m4/ax_check_link_flag.m4 \ - $(top_srcdir)/m4/ax_require_defined.m4 $(top_srcdir)/m4/lzo.m4 \ - $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/zlib.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) @@ -119,51 +214,200 @@ mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = +@LINUX_TRUE@am__EXEEXT_1 = sptps_speed$(EXEEXT) am__installdirs = "$(DESTDIR)$(sbindir)" PROGRAMS = $(sbin_PROGRAMS) -am__tincd_SOURCES_DIST = have.h system.h avl_tree.c avl_tree.h conf.c \ - conf.h connection.c connection.h device.h dropin.c dropin.h \ - dummy_device.c edge.c edge.h ethernet.h event.c event.h \ - fake-getaddrinfo.c fake-getaddrinfo.h fake-getnameinfo.c \ - fake-getnameinfo.h graph.c graph.h ipv4.h ipv6.h list.c list.h \ - logger.c logger.h meta.c meta.h multicast_device.c net.c net.h \ - net_packet.c net_setup.c net_socket.c netutl.c netutl.h node.c \ - node.h pidfile.c pidfile.h process.c process.h protocol.c \ - protocol.h protocol_auth.c protocol_edge.c protocol_misc.c \ - protocol_key.c protocol_subnet.c proxy.c proxy.h \ - raw_socket_device.c route.c route.h subnet.c subnet.h tincd.c \ - utils.c utils.h xalloc.h getopt.c getopt.h getopt1.c \ - linux/device.c bsd/device.c bsd/tunemu.c bsd/tunemu.h \ - solaris/device.c mingw/device.c mingw/common.h cygwin/device.c \ - uml_device.c vde_device.c -@GETOPT_FALSE@am__objects_1 = getopt.$(OBJEXT) getopt1.$(OBJEXT) +am__sptps_keypair_SOURCES_DIST = sptps_keypair.c utils.c utils.h \ + ed25519/ecdsagen.c ed25519/ed25519.h ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c ed25519/keypair.c \ + ed25519/precomp_data.h ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h ed25519/sign.c \ + ed25519/verify.c getopt.c getopt.h getopt1.c openssl/crypto.c \ + nolegacy/crypto.c am__dirstamp = $(am__leading_dot)dirstamp -@LINUX_TRUE@am__objects_2 = linux/device.$(OBJEXT) -@BSD_TRUE@am__objects_3 = bsd/device.$(OBJEXT) -@BSD_TRUE@@TUNEMU_TRUE@am__objects_4 = bsd/tunemu.$(OBJEXT) -@SOLARIS_TRUE@am__objects_5 = solaris/device.$(OBJEXT) -@MINGW_TRUE@am__objects_6 = mingw/device.$(OBJEXT) -@CYGWIN_TRUE@am__objects_7 = cygwin/device.$(OBJEXT) -@UML_TRUE@am__objects_8 = uml_device.$(OBJEXT) -@VDE_TRUE@am__objects_9 = vde_device.$(OBJEXT) -am_tincd_OBJECTS = avl_tree.$(OBJEXT) conf.$(OBJEXT) \ - connection.$(OBJEXT) dropin.$(OBJEXT) dummy_device.$(OBJEXT) \ - edge.$(OBJEXT) event.$(OBJEXT) fake-getaddrinfo.$(OBJEXT) \ - fake-getnameinfo.$(OBJEXT) graph.$(OBJEXT) list.$(OBJEXT) \ - logger.$(OBJEXT) meta.$(OBJEXT) multicast_device.$(OBJEXT) \ +am__objects_1 = ed25519/fe.$(OBJEXT) ed25519/ge.$(OBJEXT) \ + ed25519/key_exchange.$(OBJEXT) ed25519/keypair.$(OBJEXT) \ + ed25519/sc.$(OBJEXT) ed25519/sha512.$(OBJEXT) \ + ed25519/sign.$(OBJEXT) ed25519/verify.$(OBJEXT) +@GETOPT_FALSE@am__objects_2 = getopt.$(OBJEXT) getopt1.$(OBJEXT) +@OPENSSL_TRUE@am__objects_3 = openssl/crypto.$(OBJEXT) +@GCRYPT_TRUE@@OPENSSL_FALSE@am__objects_4 = openssl/crypto.$(OBJEXT) +@GCRYPT_FALSE@@OPENSSL_FALSE@am__objects_5 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.$(OBJEXT) +am_sptps_keypair_OBJECTS = sptps_keypair.$(OBJEXT) utils.$(OBJEXT) \ + ed25519/ecdsagen.$(OBJEXT) $(am__objects_1) $(am__objects_2) \ + $(am__objects_3) $(am__objects_4) $(am__objects_5) +sptps_keypair_OBJECTS = $(am_sptps_keypair_OBJECTS) +sptps_keypair_LDADD = $(LDADD) +am__sptps_speed_SOURCES_DIST = logger.c logger.h sptps.c sptps.h \ + sptps_speed.c utils.c utils.h ed25519/ecdh.c ed25519/ecdsa.c \ + ed25519/ecdsagen.c ed25519/ed25519.h ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c ed25519/keypair.c \ + ed25519/precomp_data.h ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h ed25519/sign.c \ + ed25519/verify.c chacha-poly1305/chacha.c \ + chacha-poly1305/chacha.h chacha-poly1305/chacha-poly1305.c \ + chacha-poly1305/chacha-poly1305.h chacha-poly1305/poly1305.c \ + chacha-poly1305/poly1305.h openssl/crypto.c openssl/digest.c \ + openssl/digest.h openssl/prf.c nolegacy/crypto.c \ + nolegacy/prf.c +am__objects_6 = chacha-poly1305/chacha.$(OBJEXT) \ + chacha-poly1305/chacha-poly1305.$(OBJEXT) \ + chacha-poly1305/poly1305.$(OBJEXT) +@OPENSSL_TRUE@am__objects_7 = openssl/crypto.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/digest.$(OBJEXT) openssl/prf.$(OBJEXT) +@GCRYPT_TRUE@@OPENSSL_FALSE@am__objects_8 = openssl/crypto.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/digest.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ openssl/prf.$(OBJEXT) +@GCRYPT_FALSE@@OPENSSL_FALSE@am__objects_9 = \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/crypto.$(OBJEXT) \ +@GCRYPT_FALSE@@OPENSSL_FALSE@ nolegacy/prf.$(OBJEXT) +am_sptps_speed_OBJECTS = logger.$(OBJEXT) sptps.$(OBJEXT) \ + sptps_speed.$(OBJEXT) utils.$(OBJEXT) ed25519/ecdh.$(OBJEXT) \ + ed25519/ecdsa.$(OBJEXT) ed25519/ecdsagen.$(OBJEXT) \ + $(am__objects_1) $(am__objects_6) $(am__objects_7) \ + $(am__objects_8) $(am__objects_9) +sptps_speed_OBJECTS = $(am_sptps_speed_OBJECTS) +sptps_speed_DEPENDENCIES = +am__sptps_test_SOURCES_DIST = logger.c logger.h sptps.c sptps.h \ + sptps_test.c utils.c utils.h ed25519/ecdh.c ed25519/ecdsa.c \ + ed25519/ed25519.h ed25519/fe.c ed25519/fe.h ed25519/fixedint.h \ + ed25519/ge.c ed25519/ge.h ed25519/key_exchange.c \ + ed25519/keypair.c ed25519/precomp_data.h ed25519/sc.c \ + ed25519/sc.h ed25519/sha512.c ed25519/sha512.h ed25519/sign.c \ + ed25519/verify.c chacha-poly1305/chacha.c \ + chacha-poly1305/chacha.h chacha-poly1305/chacha-poly1305.c \ + chacha-poly1305/chacha-poly1305.h chacha-poly1305/poly1305.c \ + chacha-poly1305/poly1305.h getopt.c getopt.h getopt1.c \ + openssl/crypto.c openssl/digest.c openssl/digest.h \ + openssl/prf.c gcrypt/cipher.c gcrypt/crypto.c gcrypt/digest.c \ + gcrypt/digest.h gcrypt/prf.c nolegacy/crypto.c nolegacy/prf.c +@GCRYPT_TRUE@@OPENSSL_FALSE@am__objects_10 = gcrypt/cipher.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.$(OBJEXT) +am_sptps_test_OBJECTS = logger.$(OBJEXT) sptps.$(OBJEXT) \ + sptps_test.$(OBJEXT) utils.$(OBJEXT) ed25519/ecdh.$(OBJEXT) \ + ed25519/ecdsa.$(OBJEXT) $(am__objects_1) $(am__objects_6) \ + $(am__objects_2) $(am__objects_7) $(am__objects_10) \ + $(am__objects_9) +sptps_test_OBJECTS = $(am_sptps_test_OBJECTS) +sptps_test_LDADD = $(LDADD) +am__tinc_SOURCES_DIST = dropin.c dropin.h fsck.c fsck.h ifconfig.c \ + ifconfig.h info.c info.h invitation.c invitation.h list.c \ + list.h names.c names.h netutl.c netutl.h script.c script.h \ + sptps.c sptps.h subnet_parse.c subnet.h tincctl.c tincctl.h \ + top.c top.h utils.c utils.h version.c version.h ed25519/ecdh.c \ + ed25519/ecdsa.c ed25519/ecdsagen.c ed25519/ed25519.h \ + ed25519/fe.c ed25519/fe.h ed25519/fixedint.h ed25519/ge.c \ + ed25519/ge.h ed25519/key_exchange.c ed25519/keypair.c \ + ed25519/precomp_data.h ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h ed25519/sign.c \ + ed25519/verify.c chacha-poly1305/chacha.c \ + chacha-poly1305/chacha.h chacha-poly1305/chacha-poly1305.c \ + chacha-poly1305/chacha-poly1305.h chacha-poly1305/poly1305.c \ + chacha-poly1305/poly1305.h getopt.c getopt.h getopt1.c \ + openssl/cipher.c openssl/crypto.c openssl/digest.c \ + openssl/digest.h openssl/prf.c openssl/rsa.c openssl/rsagen.c \ + gcrypt/cipher.c gcrypt/crypto.c gcrypt/digest.c \ + gcrypt/digest.h gcrypt/prf.c gcrypt/rsa.c gcrypt/rsagen.c \ + nolegacy/crypto.c nolegacy/prf.c +@OPENSSL_TRUE@am__objects_11 = openssl/cipher.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/crypto.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/digest.$(OBJEXT) openssl/prf.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/rsa.$(OBJEXT) openssl/rsagen.$(OBJEXT) +@GCRYPT_TRUE@@OPENSSL_FALSE@am__objects_12 = gcrypt/cipher.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsa.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsagen.$(OBJEXT) +am_tinc_OBJECTS = dropin.$(OBJEXT) fsck.$(OBJEXT) ifconfig.$(OBJEXT) \ + info.$(OBJEXT) invitation.$(OBJEXT) list.$(OBJEXT) \ + names.$(OBJEXT) netutl.$(OBJEXT) script.$(OBJEXT) \ + sptps.$(OBJEXT) subnet_parse.$(OBJEXT) tincctl.$(OBJEXT) \ + top.$(OBJEXT) utils.$(OBJEXT) version.$(OBJEXT) \ + ed25519/ecdh.$(OBJEXT) ed25519/ecdsa.$(OBJEXT) \ + ed25519/ecdsagen.$(OBJEXT) $(am__objects_1) $(am__objects_6) \ + $(am__objects_2) $(am__objects_11) $(am__objects_12) \ + $(am__objects_9) +tinc_OBJECTS = $(am_tinc_OBJECTS) +am__DEPENDENCIES_1 = +tinc_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am__tincd_SOURCES_DIST = address_cache.c address_cache.h autoconnect.c \ + autoconnect.h buffer.c buffer.h cipher.h conf.c conf.h \ + connection.c connection.h control.c control.h control_common.h \ + crypto.h device.h digest.h dropin.c dropin.h dummy_device.c \ + ecdh.h ecdsa.h ecdsagen.h edge.c edge.h ethernet.h event.c \ + event.h fd_device.c graph.c graph.h hash.c hash.h have.h \ + ipv4.h ipv6.h list.c list.h logger.c logger.h meta.c meta.h \ + multicast_device.c names.c names.h net.c net.h net_packet.c \ + net_setup.c net_socket.c netutl.c netutl.h node.c node.h prf.h \ + process.c process.h protocol.c protocol.h protocol_auth.c \ + protocol_edge.c protocol_key.c protocol_misc.c \ + protocol_subnet.c raw_socket_device.c route.c route.h rsa.h \ + rsagen.h script.c script.h splay_tree.c splay_tree.h sptps.c \ + sptps.h subnet.c subnet.h subnet_parse.c system.h tincd.c \ + utils.c utils.h xalloc.h version.c version.h ed25519/ecdh.c \ + ed25519/ecdsa.c ed25519/ed25519.h ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c ed25519/keypair.c \ + ed25519/precomp_data.h ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h ed25519/sign.c \ + ed25519/verify.c chacha-poly1305/chacha.c \ + chacha-poly1305/chacha.h chacha-poly1305/chacha-poly1305.c \ + chacha-poly1305/chacha-poly1305.h chacha-poly1305/poly1305.c \ + chacha-poly1305/poly1305.h getopt.c getopt.h getopt1.c \ + linux/device.c bsd/device.c bsd/tunemu.c bsd/tunemu.h \ + solaris/device.c mingw/device.c mingw/common.h uml_device.c \ + vde_device.c openssl/cipher.c openssl/crypto.c \ + openssl/digest.c openssl/digest.h openssl/prf.c openssl/rsa.c \ + gcrypt/cipher.c gcrypt/crypto.c gcrypt/digest.c \ + gcrypt/digest.h gcrypt/prf.c gcrypt/rsa.c nolegacy/crypto.c \ + nolegacy/prf.c upnp.h upnp.c +@LINUX_TRUE@am__objects_13 = linux/device.$(OBJEXT) +@BSD_TRUE@am__objects_14 = bsd/device.$(OBJEXT) +@BSD_TRUE@@TUNEMU_TRUE@am__objects_15 = bsd/tunemu.$(OBJEXT) +@SOLARIS_TRUE@am__objects_16 = solaris/device.$(OBJEXT) +@MINGW_TRUE@am__objects_17 = mingw/device.$(OBJEXT) +@UML_TRUE@am__objects_18 = uml_device.$(OBJEXT) +@VDE_TRUE@am__objects_19 = vde_device.$(OBJEXT) +@OPENSSL_TRUE@am__objects_20 = openssl/cipher.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/crypto.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/digest.$(OBJEXT) openssl/prf.$(OBJEXT) \ +@OPENSSL_TRUE@ openssl/rsa.$(OBJEXT) +@GCRYPT_TRUE@@OPENSSL_FALSE@am__objects_21 = gcrypt/cipher.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/crypto.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/digest.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/prf.$(OBJEXT) \ +@GCRYPT_TRUE@@OPENSSL_FALSE@ gcrypt/rsa.$(OBJEXT) +@MINIUPNPC_TRUE@am__objects_22 = upnp.$(OBJEXT) +am_tincd_OBJECTS = address_cache.$(OBJEXT) autoconnect.$(OBJEXT) \ + buffer.$(OBJEXT) conf.$(OBJEXT) connection.$(OBJEXT) \ + control.$(OBJEXT) dropin.$(OBJEXT) dummy_device.$(OBJEXT) \ + edge.$(OBJEXT) event.$(OBJEXT) fd_device.$(OBJEXT) \ + graph.$(OBJEXT) hash.$(OBJEXT) list.$(OBJEXT) logger.$(OBJEXT) \ + meta.$(OBJEXT) multicast_device.$(OBJEXT) names.$(OBJEXT) \ net.$(OBJEXT) net_packet.$(OBJEXT) net_setup.$(OBJEXT) \ net_socket.$(OBJEXT) netutl.$(OBJEXT) node.$(OBJEXT) \ - pidfile.$(OBJEXT) process.$(OBJEXT) protocol.$(OBJEXT) \ - protocol_auth.$(OBJEXT) protocol_edge.$(OBJEXT) \ - protocol_misc.$(OBJEXT) protocol_key.$(OBJEXT) \ - protocol_subnet.$(OBJEXT) proxy.$(OBJEXT) \ - raw_socket_device.$(OBJEXT) route.$(OBJEXT) subnet.$(OBJEXT) \ - tincd.$(OBJEXT) utils.$(OBJEXT) $(am__objects_1) \ - $(am__objects_2) $(am__objects_3) $(am__objects_4) \ - $(am__objects_5) $(am__objects_6) $(am__objects_7) \ - $(am__objects_8) $(am__objects_9) + process.$(OBJEXT) protocol.$(OBJEXT) protocol_auth.$(OBJEXT) \ + protocol_edge.$(OBJEXT) protocol_key.$(OBJEXT) \ + protocol_misc.$(OBJEXT) protocol_subnet.$(OBJEXT) \ + raw_socket_device.$(OBJEXT) route.$(OBJEXT) script.$(OBJEXT) \ + splay_tree.$(OBJEXT) sptps.$(OBJEXT) subnet.$(OBJEXT) \ + subnet_parse.$(OBJEXT) tincd.$(OBJEXT) utils.$(OBJEXT) \ + version.$(OBJEXT) ed25519/ecdh.$(OBJEXT) \ + ed25519/ecdsa.$(OBJEXT) $(am__objects_1) $(am__objects_6) \ + $(am__objects_2) $(am__objects_13) $(am__objects_14) \ + $(am__objects_15) $(am__objects_16) $(am__objects_17) \ + $(am__objects_18) $(am__objects_19) $(am__objects_20) \ + $(am__objects_21) $(am__objects_9) $(am__objects_22) tincd_OBJECTS = $(am_tincd_OBJECTS) -tincd_LDADD = $(LDADD) +@MINIUPNPC_TRUE@tincd_DEPENDENCIES = $(am__DEPENDENCIES_1) +tincd_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(tincd_LDFLAGS) \ + $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false @@ -179,28 +423,52 @@ am__v_at_1 = DEFAULT_INCLUDES = depcomp = $(SHELL) $(top_srcdir)/depcomp am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/avl_tree.Po ./$(DEPDIR)/conf.Po \ - ./$(DEPDIR)/connection.Po ./$(DEPDIR)/dropin.Po \ +am__depfiles_remade = ./$(DEPDIR)/address_cache.Po \ + ./$(DEPDIR)/autoconnect.Po ./$(DEPDIR)/buffer.Po \ + ./$(DEPDIR)/conf.Po ./$(DEPDIR)/connection.Po \ + ./$(DEPDIR)/control.Po ./$(DEPDIR)/dropin.Po \ ./$(DEPDIR)/dummy_device.Po ./$(DEPDIR)/edge.Po \ - ./$(DEPDIR)/event.Po ./$(DEPDIR)/fake-getaddrinfo.Po \ - ./$(DEPDIR)/fake-getnameinfo.Po ./$(DEPDIR)/getopt.Po \ + ./$(DEPDIR)/event.Po ./$(DEPDIR)/fd_device.Po \ + ./$(DEPDIR)/fsck.Po ./$(DEPDIR)/getopt.Po \ ./$(DEPDIR)/getopt1.Po ./$(DEPDIR)/graph.Po \ + ./$(DEPDIR)/hash.Po ./$(DEPDIR)/ifconfig.Po \ + ./$(DEPDIR)/info.Po ./$(DEPDIR)/invitation.Po \ ./$(DEPDIR)/list.Po ./$(DEPDIR)/logger.Po ./$(DEPDIR)/meta.Po \ - ./$(DEPDIR)/multicast_device.Po ./$(DEPDIR)/net.Po \ - ./$(DEPDIR)/net_packet.Po ./$(DEPDIR)/net_setup.Po \ - ./$(DEPDIR)/net_socket.Po ./$(DEPDIR)/netutl.Po \ - ./$(DEPDIR)/node.Po ./$(DEPDIR)/pidfile.Po \ + ./$(DEPDIR)/multicast_device.Po ./$(DEPDIR)/names.Po \ + ./$(DEPDIR)/net.Po ./$(DEPDIR)/net_packet.Po \ + ./$(DEPDIR)/net_setup.Po ./$(DEPDIR)/net_socket.Po \ + ./$(DEPDIR)/netutl.Po ./$(DEPDIR)/node.Po \ ./$(DEPDIR)/process.Po ./$(DEPDIR)/protocol.Po \ ./$(DEPDIR)/protocol_auth.Po ./$(DEPDIR)/protocol_edge.Po \ ./$(DEPDIR)/protocol_key.Po ./$(DEPDIR)/protocol_misc.Po \ - ./$(DEPDIR)/protocol_subnet.Po ./$(DEPDIR)/proxy.Po \ + ./$(DEPDIR)/protocol_subnet.Po \ ./$(DEPDIR)/raw_socket_device.Po ./$(DEPDIR)/route.Po \ - ./$(DEPDIR)/subnet.Po ./$(DEPDIR)/tincd.Po \ - ./$(DEPDIR)/uml_device.Po ./$(DEPDIR)/utils.Po \ - ./$(DEPDIR)/vde_device.Po bsd/$(DEPDIR)/device.Po \ - bsd/$(DEPDIR)/tunemu.Po cygwin/$(DEPDIR)/device.Po \ - linux/$(DEPDIR)/device.Po mingw/$(DEPDIR)/device.Po \ - solaris/$(DEPDIR)/device.Po + ./$(DEPDIR)/script.Po ./$(DEPDIR)/splay_tree.Po \ + ./$(DEPDIR)/sptps.Po ./$(DEPDIR)/sptps_keypair.Po \ + ./$(DEPDIR)/sptps_speed.Po ./$(DEPDIR)/sptps_test.Po \ + ./$(DEPDIR)/subnet.Po ./$(DEPDIR)/subnet_parse.Po \ + ./$(DEPDIR)/tincctl.Po ./$(DEPDIR)/tincd.Po ./$(DEPDIR)/top.Po \ + ./$(DEPDIR)/uml_device.Po ./$(DEPDIR)/upnp.Po \ + ./$(DEPDIR)/utils.Po ./$(DEPDIR)/vde_device.Po \ + ./$(DEPDIR)/version.Po bsd/$(DEPDIR)/device.Po \ + bsd/$(DEPDIR)/tunemu.Po \ + chacha-poly1305/$(DEPDIR)/chacha-poly1305.Po \ + chacha-poly1305/$(DEPDIR)/chacha.Po \ + chacha-poly1305/$(DEPDIR)/poly1305.Po \ + ed25519/$(DEPDIR)/ecdh.Po ed25519/$(DEPDIR)/ecdsa.Po \ + ed25519/$(DEPDIR)/ecdsagen.Po ed25519/$(DEPDIR)/fe.Po \ + ed25519/$(DEPDIR)/ge.Po ed25519/$(DEPDIR)/key_exchange.Po \ + ed25519/$(DEPDIR)/keypair.Po ed25519/$(DEPDIR)/sc.Po \ + ed25519/$(DEPDIR)/sha512.Po ed25519/$(DEPDIR)/sign.Po \ + ed25519/$(DEPDIR)/verify.Po gcrypt/$(DEPDIR)/cipher.Po \ + gcrypt/$(DEPDIR)/crypto.Po gcrypt/$(DEPDIR)/digest.Po \ + gcrypt/$(DEPDIR)/prf.Po gcrypt/$(DEPDIR)/rsa.Po \ + gcrypt/$(DEPDIR)/rsagen.Po linux/$(DEPDIR)/device.Po \ + mingw/$(DEPDIR)/device.Po nolegacy/$(DEPDIR)/crypto.Po \ + nolegacy/$(DEPDIR)/prf.Po openssl/$(DEPDIR)/cipher.Po \ + openssl/$(DEPDIR)/crypto.Po openssl/$(DEPDIR)/digest.Po \ + openssl/$(DEPDIR)/prf.Po openssl/$(DEPDIR)/rsa.Po \ + openssl/$(DEPDIR)/rsagen.Po solaris/$(DEPDIR)/device.Po am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -214,8 +482,11 @@ AM_V_CCLD = $(am__v_CCLD_@AM_V@) am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) am__v_CCLD_0 = @echo " CCLD " $@; am__v_CCLD_1 = -SOURCES = $(tincd_SOURCES) -DIST_SOURCES = $(am__tincd_SOURCES_DIST) +SOURCES = $(sptps_keypair_SOURCES) $(sptps_speed_SOURCES) \ + $(sptps_test_SOURCES) $(tinc_SOURCES) $(tincd_SOURCES) +DIST_SOURCES = $(am__sptps_keypair_SOURCES_DIST) \ + $(am__sptps_speed_SOURCES_DIST) $(am__sptps_test_SOURCES_DIST) \ + $(am__tinc_SOURCES_DIST) $(am__tincd_SOURCES_DIST) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -252,8 +523,15 @@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ @@ -262,17 +540,21 @@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ $(am__append_10) +LIBS = @LIBS@ -lm $(CODE_COVERAGE_LIBS) $(am__append_29) LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ MKDIR_P = @MKDIR_P@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ @@ -283,6 +565,8 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -339,22 +623,71 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -tincd_SOURCES = have.h system.h avl_tree.c avl_tree.h conf.c conf.h \ - connection.c connection.h device.h dropin.c dropin.h \ - dummy_device.c edge.c edge.h ethernet.h event.c event.h \ - fake-getaddrinfo.c fake-getaddrinfo.h fake-getnameinfo.c \ - fake-getnameinfo.h graph.c graph.h ipv4.h ipv6.h list.c list.h \ - logger.c logger.h meta.c meta.h multicast_device.c net.c net.h \ - net_packet.c net_setup.c net_socket.c netutl.c netutl.h node.c \ - node.h pidfile.c pidfile.h process.c process.h protocol.c \ - protocol.h protocol_auth.c protocol_edge.c protocol_misc.c \ - protocol_key.c protocol_subnet.c proxy.c proxy.h \ - raw_socket_device.c route.c route.h subnet.c subnet.h tincd.c \ - utils.c utils.h xalloc.h $(am__append_1) $(am__append_2) \ - $(am__append_3) $(am__append_4) $(am__append_5) \ - $(am__append_6) $(am__append_7) $(am__append_8) \ - $(am__append_9) -AM_CPPFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DRUNSTATEDIR=\"$(runstatedir)\" -DLOCALSTATEDIR=\"$(localstatedir)\" -I $(abs_top_builddir)/ +CLEANFILES = version_git.h +ed25519_SOURCES = \ + ed25519/ed25519.h \ + ed25519/fe.c ed25519/fe.h \ + ed25519/fixedint.h \ + ed25519/ge.c ed25519/ge.h \ + ed25519/key_exchange.c \ + ed25519/keypair.c \ + ed25519/precomp_data.h \ + ed25519/sc.c ed25519/sc.h \ + ed25519/sha512.c ed25519/sha512.h \ + ed25519/sign.c \ + ed25519/verify.c + +chacha_poly1305_SOURCES = \ + chacha-poly1305/chacha.c chacha-poly1305/chacha.h \ + chacha-poly1305/chacha-poly1305.c chacha-poly1305/chacha-poly1305.h \ + chacha-poly1305/poly1305.c chacha-poly1305/poly1305.h + +tincd_SOURCES = address_cache.c address_cache.h autoconnect.c \ + autoconnect.h buffer.c buffer.h cipher.h conf.c conf.h \ + connection.c connection.h control.c control.h control_common.h \ + crypto.h device.h digest.h dropin.c dropin.h dummy_device.c \ + ecdh.h ecdsa.h ecdsagen.h edge.c edge.h ethernet.h event.c \ + event.h fd_device.c graph.c graph.h hash.c hash.h have.h \ + ipv4.h ipv6.h list.c list.h logger.c logger.h meta.c meta.h \ + multicast_device.c names.c names.h net.c net.h net_packet.c \ + net_setup.c net_socket.c netutl.c netutl.h node.c node.h prf.h \ + process.c process.h protocol.c protocol.h protocol_auth.c \ + protocol_edge.c protocol_key.c protocol_misc.c \ + protocol_subnet.c raw_socket_device.c route.c route.h rsa.h \ + rsagen.h script.c script.h splay_tree.c splay_tree.h sptps.c \ + sptps.h subnet.c subnet.h subnet_parse.c system.h tincd.c \ + utils.c utils.h xalloc.h version.c version.h ed25519/ecdh.c \ + ed25519/ecdsa.c $(ed25519_SOURCES) $(chacha_poly1305_SOURCES) \ + $(am__append_2) $(am__append_6) $(am__append_7) \ + $(am__append_8) $(am__append_9) $(am__append_10) \ + $(am__append_11) $(am__append_12) $(am__append_13) \ + $(am__append_18) $(am__append_23) $(am__append_28) +tinc_SOURCES = dropin.c dropin.h fsck.c fsck.h ifconfig.c ifconfig.h \ + info.c info.h invitation.c invitation.h list.c list.h names.c \ + names.h netutl.c netutl.h script.c script.h sptps.c sptps.h \ + subnet_parse.c subnet.h tincctl.c tincctl.h top.c top.h \ + utils.c utils.h version.c version.h ed25519/ecdh.c \ + ed25519/ecdsa.c ed25519/ecdsagen.c $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) $(am__append_3) $(am__append_14) \ + $(am__append_19) $(am__append_24) +sptps_test_SOURCES = logger.c logger.h sptps.c sptps.h sptps_test.c \ + utils.c utils.h ed25519/ecdh.c ed25519/ecdsa.c \ + $(ed25519_SOURCES) $(chacha_poly1305_SOURCES) $(am__append_4) \ + $(am__append_15) $(am__append_20) $(am__append_25) +sptps_keypair_SOURCES = sptps_keypair.c utils.c utils.h \ + ed25519/ecdsagen.c $(ed25519_SOURCES) $(am__append_5) \ + $(am__append_16) $(am__append_21) $(am__append_26) +sptps_speed_SOURCES = logger.c logger.h sptps.c sptps.h sptps_speed.c \ + utils.c utils.h ed25519/ecdh.c ed25519/ecdsa.c \ + ed25519/ecdsagen.c $(ed25519_SOURCES) \ + $(chacha_poly1305_SOURCES) $(am__append_17) $(am__append_22) \ + $(am__append_27) +@MINIUPNPC_TRUE@tincd_LDADD = $(MINIUPNPC_LIBS) +@MINIUPNPC_TRUE@tincd_LDFLAGS = -pthread +tinc_LDADD = $(READLINE_LIBS) $(CURSES_LIBS) +sptps_speed_LDADD = -lrt +AM_CFLAGS = -DCONFDIR=\"$(sysconfdir)\" -DRUNSTATEDIR=\"$(runstatedir)\" -DLOCALSTATEDIR=\"$(localstatedir)\" -DSBINDIR=\"$(sbindir)\" -iquote. $(CODE_COVERAGE_CFLAGS) +AM_CPPFLAGS = $(CODE_COVERAGE_CPPFLAGS) all: all-am .SUFFIXES: @@ -388,6 +721,9 @@ $(top_srcdir)/configure: $(am__configure_deps) $(ACLOCAL_M4): $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(am__aclocal_m4_deps): + +clean-checkPROGRAMS: + -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS) install-sbinPROGRAMS: $(sbin_PROGRAMS) @$(NORMAL_INSTALL) @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ @@ -446,6 +782,109 @@ installcheck-sbinPROGRAMS: $(sbin_PROGRAMS) else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \ done; \ done; rm -f c$${pid}_.???; exit $$bad +ed25519/$(am__dirstamp): + @$(MKDIR_P) ed25519 + @: > ed25519/$(am__dirstamp) +ed25519/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ed25519/$(DEPDIR) + @: > ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/ecdsagen.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/fe.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/ge.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/key_exchange.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/keypair.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/sc.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/sha512.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/sign.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/verify.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +openssl/$(am__dirstamp): + @$(MKDIR_P) openssl + @: > openssl/$(am__dirstamp) +openssl/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) openssl/$(DEPDIR) + @: > openssl/$(DEPDIR)/$(am__dirstamp) +openssl/crypto.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +nolegacy/$(am__dirstamp): + @$(MKDIR_P) nolegacy + @: > nolegacy/$(am__dirstamp) +nolegacy/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) nolegacy/$(DEPDIR) + @: > nolegacy/$(DEPDIR)/$(am__dirstamp) +nolegacy/crypto.$(OBJEXT): nolegacy/$(am__dirstamp) \ + nolegacy/$(DEPDIR)/$(am__dirstamp) + +sptps_keypair$(EXEEXT): $(sptps_keypair_OBJECTS) $(sptps_keypair_DEPENDENCIES) $(EXTRA_sptps_keypair_DEPENDENCIES) + @rm -f sptps_keypair$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sptps_keypair_OBJECTS) $(sptps_keypair_LDADD) $(LIBS) +ed25519/ecdh.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +ed25519/ecdsa.$(OBJEXT): ed25519/$(am__dirstamp) \ + ed25519/$(DEPDIR)/$(am__dirstamp) +chacha-poly1305/$(am__dirstamp): + @$(MKDIR_P) chacha-poly1305 + @: > chacha-poly1305/$(am__dirstamp) +chacha-poly1305/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) chacha-poly1305/$(DEPDIR) + @: > chacha-poly1305/$(DEPDIR)/$(am__dirstamp) +chacha-poly1305/chacha.$(OBJEXT): chacha-poly1305/$(am__dirstamp) \ + chacha-poly1305/$(DEPDIR)/$(am__dirstamp) +chacha-poly1305/chacha-poly1305.$(OBJEXT): \ + chacha-poly1305/$(am__dirstamp) \ + chacha-poly1305/$(DEPDIR)/$(am__dirstamp) +chacha-poly1305/poly1305.$(OBJEXT): chacha-poly1305/$(am__dirstamp) \ + chacha-poly1305/$(DEPDIR)/$(am__dirstamp) +openssl/digest.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +openssl/prf.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +nolegacy/prf.$(OBJEXT): nolegacy/$(am__dirstamp) \ + nolegacy/$(DEPDIR)/$(am__dirstamp) + +sptps_speed$(EXEEXT): $(sptps_speed_OBJECTS) $(sptps_speed_DEPENDENCIES) $(EXTRA_sptps_speed_DEPENDENCIES) + @rm -f sptps_speed$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sptps_speed_OBJECTS) $(sptps_speed_LDADD) $(LIBS) +gcrypt/$(am__dirstamp): + @$(MKDIR_P) gcrypt + @: > gcrypt/$(am__dirstamp) +gcrypt/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) gcrypt/$(DEPDIR) + @: > gcrypt/$(DEPDIR)/$(am__dirstamp) +gcrypt/cipher.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) +gcrypt/crypto.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) +gcrypt/digest.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) +gcrypt/prf.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) + +sptps_test$(EXEEXT): $(sptps_test_OBJECTS) $(sptps_test_DEPENDENCIES) $(EXTRA_sptps_test_DEPENDENCIES) + @rm -f sptps_test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(sptps_test_OBJECTS) $(sptps_test_LDADD) $(LIBS) +openssl/cipher.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +openssl/rsa.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +openssl/rsagen.$(OBJEXT): openssl/$(am__dirstamp) \ + openssl/$(DEPDIR)/$(am__dirstamp) +gcrypt/rsa.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) +gcrypt/rsagen.$(OBJEXT): gcrypt/$(am__dirstamp) \ + gcrypt/$(DEPDIR)/$(am__dirstamp) + +tinc$(EXEEXT): $(tinc_OBJECTS) $(tinc_DEPENDENCIES) $(EXTRA_tinc_DEPENDENCIES) + @rm -f tinc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tinc_OBJECTS) $(tinc_LDADD) $(LIBS) linux/$(am__dirstamp): @$(MKDIR_P) linux @: > linux/$(am__dirstamp) @@ -480,53 +919,56 @@ mingw/$(DEPDIR)/$(am__dirstamp): @: > mingw/$(DEPDIR)/$(am__dirstamp) mingw/device.$(OBJEXT): mingw/$(am__dirstamp) \ mingw/$(DEPDIR)/$(am__dirstamp) -cygwin/$(am__dirstamp): - @$(MKDIR_P) cygwin - @: > cygwin/$(am__dirstamp) -cygwin/$(DEPDIR)/$(am__dirstamp): - @$(MKDIR_P) cygwin/$(DEPDIR) - @: > cygwin/$(DEPDIR)/$(am__dirstamp) -cygwin/device.$(OBJEXT): cygwin/$(am__dirstamp) \ - cygwin/$(DEPDIR)/$(am__dirstamp) tincd$(EXEEXT): $(tincd_OBJECTS) $(tincd_DEPENDENCIES) $(EXTRA_tincd_DEPENDENCIES) @rm -f tincd$(EXEEXT) - $(AM_V_CCLD)$(LINK) $(tincd_OBJECTS) $(tincd_LDADD) $(LIBS) + $(AM_V_CCLD)$(tincd_LINK) $(tincd_OBJECTS) $(tincd_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) -rm -f bsd/*.$(OBJEXT) - -rm -f cygwin/*.$(OBJEXT) + -rm -f chacha-poly1305/*.$(OBJEXT) + -rm -f ed25519/*.$(OBJEXT) + -rm -f gcrypt/*.$(OBJEXT) -rm -f linux/*.$(OBJEXT) -rm -f mingw/*.$(OBJEXT) + -rm -f nolegacy/*.$(OBJEXT) + -rm -f openssl/*.$(OBJEXT) -rm -f solaris/*.$(OBJEXT) distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/avl_tree.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/address_cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/autoconnect.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buffer.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/connection.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/control.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dropin.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dummy_device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/edge.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/event.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fake-getaddrinfo.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fake-getnameinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fd_device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsck.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt1.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/graph.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ifconfig.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/invitation.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meta.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/multicast_device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/names.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_packet.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_setup.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_socket.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/netutl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/node.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pidfile.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/process.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_auth.Po@am__quote@ # am--include-marker @@ -534,19 +976,56 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_key.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_misc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_subnet.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proxy.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raw_socket_device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/route.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/script.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/splay_tree.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps_keypair.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps_speed.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps_test.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subnet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subnet_parse.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tincctl.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tincd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/top.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uml_device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upnp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vde_device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/version.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bsd/$(DEPDIR)/device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@bsd/$(DEPDIR)/tunemu.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@cygwin/$(DEPDIR)/device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@chacha-poly1305/$(DEPDIR)/chacha-poly1305.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@chacha-poly1305/$(DEPDIR)/chacha.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@chacha-poly1305/$(DEPDIR)/poly1305.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/ecdh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/ecdsa.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/ecdsagen.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/fe.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/ge.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/key_exchange.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/keypair.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/sc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/sha512.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/sign.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ed25519/$(DEPDIR)/verify.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/cipher.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/digest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/prf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/rsa.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gcrypt/$(DEPDIR)/rsagen.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@linux/$(DEPDIR)/device.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mingw/$(DEPDIR)/device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@nolegacy/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@nolegacy/$(DEPDIR)/prf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/cipher.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/digest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/prf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/rsa.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@openssl/$(DEPDIR)/rsagen.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@solaris/$(DEPDIR)/device.Po@am__quote@ # am--include-marker $(am__depfiles_remade): @@ -657,6 +1136,7 @@ distdir-am: $(DISTFILES) fi; \ done check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) check: check-am all-am: Makefile $(PROGRAMS) installdirs: @@ -685,18 +1165,27 @@ install-strip: mostlyclean-generic: clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) -rm -f bsd/$(DEPDIR)/$(am__dirstamp) -rm -f bsd/$(am__dirstamp) - -rm -f cygwin/$(DEPDIR)/$(am__dirstamp) - -rm -f cygwin/$(am__dirstamp) + -rm -f chacha-poly1305/$(DEPDIR)/$(am__dirstamp) + -rm -f chacha-poly1305/$(am__dirstamp) + -rm -f ed25519/$(DEPDIR)/$(am__dirstamp) + -rm -f ed25519/$(am__dirstamp) + -rm -f gcrypt/$(DEPDIR)/$(am__dirstamp) + -rm -f gcrypt/$(am__dirstamp) -rm -f linux/$(DEPDIR)/$(am__dirstamp) -rm -f linux/$(am__dirstamp) -rm -f mingw/$(DEPDIR)/$(am__dirstamp) -rm -f mingw/$(am__dirstamp) + -rm -f nolegacy/$(DEPDIR)/$(am__dirstamp) + -rm -f nolegacy/$(am__dirstamp) + -rm -f openssl/$(DEPDIR)/$(am__dirstamp) + -rm -f openssl/$(am__dirstamp) -rm -f solaris/$(DEPDIR)/$(am__dirstamp) -rm -f solaris/$(am__dirstamp) @@ -705,32 +1194,40 @@ maintainer-clean-generic: @echo "it deletes files that may require special tools to rebuild." clean: clean-am -clean-am: clean-generic clean-sbinPROGRAMS mostlyclean-am +clean-am: clean-checkPROGRAMS clean-generic clean-sbinPROGRAMS \ + mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/avl_tree.Po + -rm -f ./$(DEPDIR)/address_cache.Po + -rm -f ./$(DEPDIR)/autoconnect.Po + -rm -f ./$(DEPDIR)/buffer.Po -rm -f ./$(DEPDIR)/conf.Po -rm -f ./$(DEPDIR)/connection.Po + -rm -f ./$(DEPDIR)/control.Po -rm -f ./$(DEPDIR)/dropin.Po -rm -f ./$(DEPDIR)/dummy_device.Po -rm -f ./$(DEPDIR)/edge.Po -rm -f ./$(DEPDIR)/event.Po - -rm -f ./$(DEPDIR)/fake-getaddrinfo.Po - -rm -f ./$(DEPDIR)/fake-getnameinfo.Po + -rm -f ./$(DEPDIR)/fd_device.Po + -rm -f ./$(DEPDIR)/fsck.Po -rm -f ./$(DEPDIR)/getopt.Po -rm -f ./$(DEPDIR)/getopt1.Po -rm -f ./$(DEPDIR)/graph.Po + -rm -f ./$(DEPDIR)/hash.Po + -rm -f ./$(DEPDIR)/ifconfig.Po + -rm -f ./$(DEPDIR)/info.Po + -rm -f ./$(DEPDIR)/invitation.Po -rm -f ./$(DEPDIR)/list.Po -rm -f ./$(DEPDIR)/logger.Po -rm -f ./$(DEPDIR)/meta.Po -rm -f ./$(DEPDIR)/multicast_device.Po + -rm -f ./$(DEPDIR)/names.Po -rm -f ./$(DEPDIR)/net.Po -rm -f ./$(DEPDIR)/net_packet.Po -rm -f ./$(DEPDIR)/net_setup.Po -rm -f ./$(DEPDIR)/net_socket.Po -rm -f ./$(DEPDIR)/netutl.Po -rm -f ./$(DEPDIR)/node.Po - -rm -f ./$(DEPDIR)/pidfile.Po -rm -f ./$(DEPDIR)/process.Po -rm -f ./$(DEPDIR)/protocol.Po -rm -f ./$(DEPDIR)/protocol_auth.Po @@ -738,19 +1235,56 @@ distclean: distclean-am -rm -f ./$(DEPDIR)/protocol_key.Po -rm -f ./$(DEPDIR)/protocol_misc.Po -rm -f ./$(DEPDIR)/protocol_subnet.Po - -rm -f ./$(DEPDIR)/proxy.Po -rm -f ./$(DEPDIR)/raw_socket_device.Po -rm -f ./$(DEPDIR)/route.Po + -rm -f ./$(DEPDIR)/script.Po + -rm -f ./$(DEPDIR)/splay_tree.Po + -rm -f ./$(DEPDIR)/sptps.Po + -rm -f ./$(DEPDIR)/sptps_keypair.Po + -rm -f ./$(DEPDIR)/sptps_speed.Po + -rm -f ./$(DEPDIR)/sptps_test.Po -rm -f ./$(DEPDIR)/subnet.Po + -rm -f ./$(DEPDIR)/subnet_parse.Po + -rm -f ./$(DEPDIR)/tincctl.Po -rm -f ./$(DEPDIR)/tincd.Po + -rm -f ./$(DEPDIR)/top.Po -rm -f ./$(DEPDIR)/uml_device.Po + -rm -f ./$(DEPDIR)/upnp.Po -rm -f ./$(DEPDIR)/utils.Po -rm -f ./$(DEPDIR)/vde_device.Po + -rm -f ./$(DEPDIR)/version.Po -rm -f bsd/$(DEPDIR)/device.Po -rm -f bsd/$(DEPDIR)/tunemu.Po - -rm -f cygwin/$(DEPDIR)/device.Po + -rm -f chacha-poly1305/$(DEPDIR)/chacha-poly1305.Po + -rm -f chacha-poly1305/$(DEPDIR)/chacha.Po + -rm -f chacha-poly1305/$(DEPDIR)/poly1305.Po + -rm -f ed25519/$(DEPDIR)/ecdh.Po + -rm -f ed25519/$(DEPDIR)/ecdsa.Po + -rm -f ed25519/$(DEPDIR)/ecdsagen.Po + -rm -f ed25519/$(DEPDIR)/fe.Po + -rm -f ed25519/$(DEPDIR)/ge.Po + -rm -f ed25519/$(DEPDIR)/key_exchange.Po + -rm -f ed25519/$(DEPDIR)/keypair.Po + -rm -f ed25519/$(DEPDIR)/sc.Po + -rm -f ed25519/$(DEPDIR)/sha512.Po + -rm -f ed25519/$(DEPDIR)/sign.Po + -rm -f ed25519/$(DEPDIR)/verify.Po + -rm -f gcrypt/$(DEPDIR)/cipher.Po + -rm -f gcrypt/$(DEPDIR)/crypto.Po + -rm -f gcrypt/$(DEPDIR)/digest.Po + -rm -f gcrypt/$(DEPDIR)/prf.Po + -rm -f gcrypt/$(DEPDIR)/rsa.Po + -rm -f gcrypt/$(DEPDIR)/rsagen.Po -rm -f linux/$(DEPDIR)/device.Po -rm -f mingw/$(DEPDIR)/device.Po + -rm -f nolegacy/$(DEPDIR)/crypto.Po + -rm -f nolegacy/$(DEPDIR)/prf.Po + -rm -f openssl/$(DEPDIR)/cipher.Po + -rm -f openssl/$(DEPDIR)/crypto.Po + -rm -f openssl/$(DEPDIR)/digest.Po + -rm -f openssl/$(DEPDIR)/prf.Po + -rm -f openssl/$(DEPDIR)/rsa.Po + -rm -f openssl/$(DEPDIR)/rsagen.Po -rm -f solaris/$(DEPDIR)/device.Po -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ @@ -797,29 +1331,36 @@ install-ps-am: installcheck-am: installcheck-sbinPROGRAMS maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/avl_tree.Po + -rm -f ./$(DEPDIR)/address_cache.Po + -rm -f ./$(DEPDIR)/autoconnect.Po + -rm -f ./$(DEPDIR)/buffer.Po -rm -f ./$(DEPDIR)/conf.Po -rm -f ./$(DEPDIR)/connection.Po + -rm -f ./$(DEPDIR)/control.Po -rm -f ./$(DEPDIR)/dropin.Po -rm -f ./$(DEPDIR)/dummy_device.Po -rm -f ./$(DEPDIR)/edge.Po -rm -f ./$(DEPDIR)/event.Po - -rm -f ./$(DEPDIR)/fake-getaddrinfo.Po - -rm -f ./$(DEPDIR)/fake-getnameinfo.Po + -rm -f ./$(DEPDIR)/fd_device.Po + -rm -f ./$(DEPDIR)/fsck.Po -rm -f ./$(DEPDIR)/getopt.Po -rm -f ./$(DEPDIR)/getopt1.Po -rm -f ./$(DEPDIR)/graph.Po + -rm -f ./$(DEPDIR)/hash.Po + -rm -f ./$(DEPDIR)/ifconfig.Po + -rm -f ./$(DEPDIR)/info.Po + -rm -f ./$(DEPDIR)/invitation.Po -rm -f ./$(DEPDIR)/list.Po -rm -f ./$(DEPDIR)/logger.Po -rm -f ./$(DEPDIR)/meta.Po -rm -f ./$(DEPDIR)/multicast_device.Po + -rm -f ./$(DEPDIR)/names.Po -rm -f ./$(DEPDIR)/net.Po -rm -f ./$(DEPDIR)/net_packet.Po -rm -f ./$(DEPDIR)/net_setup.Po -rm -f ./$(DEPDIR)/net_socket.Po -rm -f ./$(DEPDIR)/netutl.Po -rm -f ./$(DEPDIR)/node.Po - -rm -f ./$(DEPDIR)/pidfile.Po -rm -f ./$(DEPDIR)/process.Po -rm -f ./$(DEPDIR)/protocol.Po -rm -f ./$(DEPDIR)/protocol_auth.Po @@ -827,19 +1368,56 @@ maintainer-clean: maintainer-clean-am -rm -f ./$(DEPDIR)/protocol_key.Po -rm -f ./$(DEPDIR)/protocol_misc.Po -rm -f ./$(DEPDIR)/protocol_subnet.Po - -rm -f ./$(DEPDIR)/proxy.Po -rm -f ./$(DEPDIR)/raw_socket_device.Po -rm -f ./$(DEPDIR)/route.Po + -rm -f ./$(DEPDIR)/script.Po + -rm -f ./$(DEPDIR)/splay_tree.Po + -rm -f ./$(DEPDIR)/sptps.Po + -rm -f ./$(DEPDIR)/sptps_keypair.Po + -rm -f ./$(DEPDIR)/sptps_speed.Po + -rm -f ./$(DEPDIR)/sptps_test.Po -rm -f ./$(DEPDIR)/subnet.Po + -rm -f ./$(DEPDIR)/subnet_parse.Po + -rm -f ./$(DEPDIR)/tincctl.Po -rm -f ./$(DEPDIR)/tincd.Po + -rm -f ./$(DEPDIR)/top.Po -rm -f ./$(DEPDIR)/uml_device.Po + -rm -f ./$(DEPDIR)/upnp.Po -rm -f ./$(DEPDIR)/utils.Po -rm -f ./$(DEPDIR)/vde_device.Po + -rm -f ./$(DEPDIR)/version.Po -rm -f bsd/$(DEPDIR)/device.Po -rm -f bsd/$(DEPDIR)/tunemu.Po - -rm -f cygwin/$(DEPDIR)/device.Po + -rm -f chacha-poly1305/$(DEPDIR)/chacha-poly1305.Po + -rm -f chacha-poly1305/$(DEPDIR)/chacha.Po + -rm -f chacha-poly1305/$(DEPDIR)/poly1305.Po + -rm -f ed25519/$(DEPDIR)/ecdh.Po + -rm -f ed25519/$(DEPDIR)/ecdsa.Po + -rm -f ed25519/$(DEPDIR)/ecdsagen.Po + -rm -f ed25519/$(DEPDIR)/fe.Po + -rm -f ed25519/$(DEPDIR)/ge.Po + -rm -f ed25519/$(DEPDIR)/key_exchange.Po + -rm -f ed25519/$(DEPDIR)/keypair.Po + -rm -f ed25519/$(DEPDIR)/sc.Po + -rm -f ed25519/$(DEPDIR)/sha512.Po + -rm -f ed25519/$(DEPDIR)/sign.Po + -rm -f ed25519/$(DEPDIR)/verify.Po + -rm -f gcrypt/$(DEPDIR)/cipher.Po + -rm -f gcrypt/$(DEPDIR)/crypto.Po + -rm -f gcrypt/$(DEPDIR)/digest.Po + -rm -f gcrypt/$(DEPDIR)/prf.Po + -rm -f gcrypt/$(DEPDIR)/rsa.Po + -rm -f gcrypt/$(DEPDIR)/rsagen.Po -rm -f linux/$(DEPDIR)/device.Po -rm -f mingw/$(DEPDIR)/device.Po + -rm -f nolegacy/$(DEPDIR)/crypto.Po + -rm -f nolegacy/$(DEPDIR)/prf.Po + -rm -f openssl/$(DEPDIR)/cipher.Po + -rm -f openssl/$(DEPDIR)/crypto.Po + -rm -f openssl/$(DEPDIR)/digest.Po + -rm -f openssl/$(DEPDIR)/prf.Po + -rm -f openssl/$(DEPDIR)/rsa.Po + -rm -f openssl/$(DEPDIR)/rsagen.Po -rm -f solaris/$(DEPDIR)/device.Po -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -858,25 +1436,36 @@ ps-am: uninstall-am: uninstall-sbinPROGRAMS -.MAKE: install-am install-strip +.MAKE: check-am install-am install-strip .PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ - distclean distclean-compile distclean-generic distclean-tags \ - distdir dvi dvi-am html html-am info info-am install \ - install-am install-data install-data-am install-dvi \ - install-dvi-am install-exec install-exec-am install-html \ - install-html-am install-info install-info-am install-man \ - install-pdf install-pdf-am install-ps install-ps-am \ - install-sbinPROGRAMS install-strip installcheck \ - installcheck-am installcheck-sbinPROGRAMS installdirs \ - maintainer-clean maintainer-clean-generic mostlyclean \ - mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \ - tags tags-am uninstall uninstall-am uninstall-sbinPROGRAMS + clean-checkPROGRAMS clean-generic clean-sbinPROGRAMS \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-sbinPROGRAMS install-strip \ + installcheck installcheck-am installcheck-sbinPROGRAMS \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-sbinPROGRAMS .PRECIOUS: Makefile +.PHONY: version-stamp +version-stamp: + +version_git.h: version-stamp + $(AM_V_GEN)echo >$@ + @-(cd $(srcdir) && git describe 2>/dev/null >/dev/null) && echo '#define GIT_DESCRIPTION "'`(cd $(srcdir) && git describe) | sed 's/release-//'`'"' >$@ ||: +${srcdir}/version.c: version_git.h + +@BSD_TRUE@version.c: ${srcdir}/version.c + # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: diff --git a/src/address_cache.c b/src/address_cache.c new file mode 100644 index 0000000..b6d48d0 --- /dev/null +++ b/src/address_cache.c @@ -0,0 +1,281 @@ +/* + address_cache.c -- Manage cache of recently seen addresses + Copyright (C) 2018 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 "address_cache.h" +#include "conf.h" +#include "names.h" +#include "netutl.h" +#include "xalloc.h" + +static const unsigned int NOT_CACHED = -1; + +// Find edges pointing to this node, and use them to build a list of unique, known addresses. +static struct addrinfo *get_known_addresses(node_t *n) { + struct addrinfo *ai = NULL; + struct addrinfo *oai = NULL; + + for splay_each(edge_t, e, n->edge_tree) { + if(!e->reverse) { + continue; + } + + bool found = false; + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + if(!sockaddrcmp(&e->reverse->address, (sockaddr_t *)aip->ai_addr)) { + found = true; + break; + } + } + + if(found) { + continue; + } + + oai = ai; + ai = xzalloc(sizeof(*ai)); + ai->ai_family = e->reverse->address.sa.sa_family; + ai->ai_socktype = SOCK_STREAM; + ai->ai_protocol = IPPROTO_TCP; + ai->ai_addrlen = SALEN(e->reverse->address.sa); + ai->ai_addr = xmalloc(ai->ai_addrlen); + memcpy(ai->ai_addr, &e->reverse->address, ai->ai_addrlen); + ai->ai_next = oai; + } + + return ai; +} + +static void free_known_addresses(struct addrinfo *ai) { + for(struct addrinfo *aip = ai, *next; aip; aip = next) { + next = aip->ai_next; + free(aip); + } +} + +static unsigned int find_cached(address_cache_t *cache, const sockaddr_t *sa) { + for(unsigned int i = 0; i < cache->data.used; i++) + if(!sockaddrcmp(&cache->data.address[i], sa)) { + return i; + } + + return NOT_CACHED; +} + +void add_recent_address(address_cache_t *cache, const sockaddr_t *sa) { + // Check if it's already cached + unsigned int pos = find_cached(cache, sa); + + // It's in the first spot, so nothing to do + if(pos == 0) { + return; + } + + // Shift everything, move/add the address to the first slot + if(pos == NOT_CACHED) { + if(cache->data.used < MAX_CACHED_ADDRESSES) { + cache->data.used++; + } + + pos = cache->data.used - 1; + } + + memmove(&cache->data.address[1], &cache->data.address[0], pos * sizeof(cache->data.address[0])); + + cache->data.address[0] = *sa; + + // Write the cache + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, cache->node->name); + FILE *fp = fopen(fname, "wb"); + + if(fp) { + fwrite(&cache->data, sizeof(cache->data), 1, fp); + fclose(fp); + } +} + +const sockaddr_t *get_recent_address(address_cache_t *cache) { + // Check if there is an address in our cache of recently seen addresses + if(cache->tried < cache->data.used) { + return &cache->data.address[cache->tried++]; + } + + // Next, check any recently seen addresses not in our cache + while(cache->tried == cache->data.used) { + if(!cache->ai) { + cache->aip = cache->ai = get_known_addresses(cache->node); + } + + if(cache->ai) { + if(cache->aip) { + sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr; + cache->aip = cache->aip->ai_next; + + if(find_cached(cache, sa) != NOT_CACHED) { + continue; + } + + return sa; + } else { + free_known_addresses(cache->ai); + cache->ai = NULL; + } + } + + cache->tried++; + } + + // Otherwise, check if there are any known Address statements + if(!cache->config_tree) { + init_configuration(&cache->config_tree); + read_host_config(cache->config_tree, cache->node->name, false); + cache->cfg = lookup_config(cache->config_tree, "Address"); + } + + while(cache->cfg && !cache->aip) { + char *address, *port; + + get_config_string(cache->cfg, &address); + + char *space = strchr(address, ' '); + + if(space) { + port = xstrdup(space + 1); + *space = 0; + } else { + if(!get_config_string(lookup_config(cache->config_tree, "Port"), &port)) { + port = xstrdup("655"); + } + } + + if(cache->ai) { + free_known_addresses(cache->ai); + } + + cache->aip = cache->ai = str2addrinfo(address, port, SOCK_STREAM); + + if(cache->ai) { + struct addrinfo *ai = NULL; + + for(; cache->aip; cache->aip = cache->aip->ai_next) { + struct addrinfo *oai = ai; + + ai = xzalloc(sizeof(*ai)); + ai->ai_family = cache->aip->ai_family; + ai->ai_socktype = cache->aip->ai_socktype; + ai->ai_protocol = cache->aip->ai_protocol; + ai->ai_addrlen = cache->aip->ai_addrlen; + ai->ai_addr = xmalloc(ai->ai_addrlen); + memcpy(ai->ai_addr, cache->aip->ai_addr, ai->ai_addrlen); + ai->ai_next = oai; + } + + freeaddrinfo(cache->ai); + cache->aip = cache->ai = ai; + } + + free(address); + free(port); + + cache->cfg = lookup_config_next(cache->config_tree, cache->cfg); + } + + if(cache->ai) { + if(cache->aip) { + sockaddr_t *sa = (sockaddr_t *)cache->aip->ai_addr; + + cache->aip = cache->aip->ai_next; + return sa; + } else { + free_known_addresses(cache->ai); + cache->ai = NULL; + } + } + + // We're all out of addresses. + exit_configuration(&cache->config_tree); + return false; +} + +address_cache_t *open_address_cache(node_t *node) { + address_cache_t *cache = xmalloc(sizeof(*cache)); + cache->node = node; + + // Try to open an existing address cache + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "cache" SLASH "%s", confbase, node->name); + FILE *fp = fopen(fname, "rb"); + + if(!fp || fread(&cache->data, sizeof(cache->data), 1, fp) != 1 || cache->data.version != ADDRESS_CACHE_VERSION) { + memset(&cache->data, 0, sizeof(cache->data)); + } + + if(fp) { + fclose(fp); + } + + // Ensure we have a valid state + cache->config_tree = NULL; + cache->cfg = NULL; + cache->ai = NULL; + cache->aip = NULL; + cache->tried = 0; + cache->data.version = ADDRESS_CACHE_VERSION; + + if(cache->data.used > MAX_CACHED_ADDRESSES) { + cache->data.used = 0; + } + + return cache; +} + +void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa) { + if(sa) { + add_recent_address(cache, sa); + } + + if(cache->config_tree) { + exit_configuration(&cache->config_tree); + } + + if(cache->ai) { + free_known_addresses(cache->ai); + } + + cache->config_tree = NULL; + cache->cfg = NULL; + cache->ai = NULL; + cache->aip = NULL; + cache->tried = 0; +} + +void close_address_cache(address_cache_t *cache) { + if(cache->config_tree) { + exit_configuration(&cache->config_tree); + } + + if(cache->ai) { + free_known_addresses(cache->ai); + } + + free(cache); +} diff --git a/src/address_cache.h b/src/address_cache.h new file mode 100644 index 0000000..4ce6ec5 --- /dev/null +++ b/src/address_cache.h @@ -0,0 +1,50 @@ +#ifndef TINC_ADDRESS_CACHE_H +#define TINC_ADDRESS_CACHE_H + +/* + address_cache.h -- header for address_cache.c + Copyright (C) 2018 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 "net.h" + +#define MAX_CACHED_ADDRESSES 8 +#define ADDRESS_CACHE_VERSION 1 + +typedef struct address_cache_t { + struct node_t *node; + struct splay_tree_t *config_tree; + struct config_t *cfg; + struct addrinfo *ai; + struct addrinfo *aip; + unsigned int tried; + + struct { + unsigned int version; + unsigned int used; + sockaddr_t address[MAX_CACHED_ADDRESSES]; + } data; +} address_cache_t; + +void add_recent_address(address_cache_t *cache, const sockaddr_t *sa); +const sockaddr_t *get_recent_address(address_cache_t *cache); + +address_cache_t *open_address_cache(struct node_t *node); +void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa); +void close_address_cache(address_cache_t *cache); + +#endif diff --git a/src/autoconnect.c b/src/autoconnect.c new file mode 100644 index 0000000..d25d65e --- /dev/null +++ b/src/autoconnect.c @@ -0,0 +1,194 @@ +/* + autoconnect.c -- automatic connection establishment + Copyright (C) 2017 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 "connection.h" +#include "logger.h" +#include "node.h" +#include "xalloc.h" + +static void make_new_connection() { + /* Select a random node we haven't connected to yet. */ + int count = 0; + + for splay_each(node_t, n, node_tree) { + if(n == myself || n->connection || !(n->status.has_address || n->status.reachable)) { + continue; + } + + count++; + } + + if(!count) { + return; + } + + int r = rand() % count; + + for splay_each(node_t, n, node_tree) { + if(n == myself || n->connection || !(n->status.has_address || n->status.reachable)) { + continue; + } + + if(r--) { + continue; + } + + bool found = false; + + for list_each(outgoing_t, outgoing, outgoing_list) { + if(outgoing->node == n) { + found = true; + break; + } + } + + if(!found) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name); + outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); + outgoing->node = n; + list_insert_tail(outgoing_list, outgoing); + setup_outgoing_connection(outgoing, false); + } + + break; + } +} + +static void connect_to_unreachable() { + /* Select a random known node. The rationale is that if there are many + * reachable nodes, and only a few unreachable nodes, we don't want all + * reachable nodes to try to connect to the unreachable ones at the + * same time. This way, we back off automatically. Conversely, if there + * are only a few reachable nodes, and many unreachable ones, we're + * going to try harder to connect to them. */ + + int r = rand() % node_tree->count; + + for splay_each(node_t, n, node_tree) { + if(r--) { + continue; + } + + /* Is it unreachable and do we know an address for it? If not, return. */ + if(n == myself || n->connection || n->status.reachable || !n->status.has_address) { + return; + } + + /* Are we already trying to make an outgoing connection to it? If so, return. */ + for list_each(outgoing_t, outgoing, outgoing_list) { + if(outgoing->node == n) { + return; + } + } + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Autoconnecting to %s", n->name); + outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); + outgoing->node = n; + list_insert_tail(outgoing_list, outgoing); + setup_outgoing_connection(outgoing, false); + + return; + } +} + +static void drop_superfluous_outgoing_connection() { + /* Choose a random outgoing connection to a node that has at least one other connection. */ + int count = 0; + + for list_each(connection_t, c, connection_list) { + if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2) { + continue; + } + + count++; + } + + if(!count) { + return; + } + + int r = rand() % count; + + for list_each(connection_t, c, connection_list) { + if(!c->edge || !c->outgoing || !c->node || c->node->edge_tree->count < 2) { + continue; + } + + if(r--) { + continue; + } + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name); + list_delete(outgoing_list, c->outgoing); + c->outgoing = NULL; + terminate_connection(c, c->edge); + break; + } +} + +static void drop_superfluous_pending_connections() { + for list_each(outgoing_t, o, outgoing_list) { + /* Only look for connections that are waiting to be retried later. */ + bool found = false; + + for list_each(connection_t, c, connection_list) { + if(c->outgoing == o) { + found = true; + break; + } + } + + if(found) { + continue; + } + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Cancelled outgoing connection to %s", o->node->name); + list_delete_node(outgoing_list, node); + } +} + +void do_autoconnect() { + /* Count number of active connections. */ + int nc = 0; + + for list_each(connection_t, c, connection_list) { + if(c->edge) { + nc++; + } + } + + /* Less than 3 connections? Eagerly try to make a new one. */ + if(nc < 3) { + make_new_connection(); + return; + } + + /* More than 3 connections? See if we can get rid of a superfluous one. */ + if(nc > 3) { + drop_superfluous_outgoing_connection(); + } + + /* Drop pending outgoing connections from the outgoing list. */ + drop_superfluous_pending_connections(); + + /* Check if there are unreachable nodes that we should try to connect to. */ + connect_to_unreachable(); +} diff --git a/src/autoconnect.h b/src/autoconnect.h new file mode 100644 index 0000000..82a5043 --- /dev/null +++ b/src/autoconnect.h @@ -0,0 +1,25 @@ +#ifndef TINC_AUTOCONNECT_H +#define TINC_AUTOCONNECT_H + +/* + autoconnect.h -- header for autoconnect.c + Copyright (C) 2017 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. +*/ + +extern void do_autoconnect(void); + +#endif diff --git a/src/avl_tree.c b/src/avl_tree.c deleted file mode 100644 index 96d3d43..0000000 --- a/src/avl_tree.c +++ /dev/null @@ -1,757 +0,0 @@ -/* - avl_tree.c -- avl_ tree and linked list convenience - Copyright (C) 1998 Michael H. Buselli - 2000-2005 Ivo Timmermans, - 2000-2015 Guus Sliepen - 2000-2005 Wessel Dankers - - 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. - - Original AVL tree library by Michael H. Buselli . - - Modified 2000-11-28 by Wessel Dankers to use counts - instead of depths, to add the ->next and ->prev and to generally obfuscate - the code. Mail me if you found a bug. - - Cleaned up and incorporated some of the ideas from the red-black tree - library for inclusion into tinc (https://www.tinc-vpn.org/) by - Guus Sliepen . -*/ - -#include "system.h" - -#include "avl_tree.h" -#include "xalloc.h" - -#ifdef AVL_COUNT -#define AVL_NODE_COUNT(n) ((n) ? (n)->count : 0) -#define AVL_L_COUNT(n) (AVL_NODE_COUNT((n)->left)) -#define AVL_R_COUNT(n) (AVL_NODE_COUNT((n)->right)) -#define AVL_CALC_COUNT(n) (AVL_L_COUNT(n) + AVL_R_COUNT(n) + 1) -#endif - -#ifdef AVL_DEPTH -#define AVL_NODE_DEPTH(n) ((n) ? (n)->depth : 0) -#define L_AVL_DEPTH(n) (AVL_NODE_DEPTH((n)->left)) -#define R_AVL_DEPTH(n) (AVL_NODE_DEPTH((n)->right)) -#define AVL_CALC_DEPTH(n) ((L_AVL_DEPTH(n)>R_AVL_DEPTH(n)?L_AVL_DEPTH(n):R_AVL_DEPTH(n)) + 1) -#endif - -#ifndef AVL_DEPTH -static int lg(unsigned int u) __attribute__((__const__)); - -static int lg(unsigned int u) { - int r = 1; - - if(!u) { - return 0; - } - - if(u & 0xffff0000) { - u >>= 16; - r += 16; - } - - if(u & 0x0000ff00) { - u >>= 8; - r += 8; - } - - if(u & 0x000000f0) { - u >>= 4; - r += 4; - } - - if(u & 0x0000000c) { - u >>= 2; - r += 2; - } - - if(u & 0x00000002) { - r++; - } - - return r; -} -#endif - -/* Internal helper functions */ - -static int avl_check_balance(const avl_node_t *node) { -#ifdef AVL_DEPTH - int d; - - d = R_AVL_DEPTH(node) - L_AVL_DEPTH(node); - - return d < -1 ? -1 : d > 1 ? 1 : 0; -#else - /* int d; - * d = lg(AVL_R_COUNT(node)) - lg(AVL_L_COUNT(node)); - * d = d<-1?-1:d>1?1:0; - */ - int pl, r; - - pl = lg(AVL_L_COUNT(node)); - r = AVL_R_COUNT(node); - - if(r >> pl + 1) { - return 1; - } - - if(pl < 2 || r >> pl - 2) { - return 0; - } - - return -1; -#endif -} - -static void avl_rebalance(avl_tree_t *tree, avl_node_t *node) { - avl_node_t *child; - avl_node_t *gchild; - avl_node_t *parent; - avl_node_t **superparent; - - while(node) { - parent = node->parent; - - superparent = - parent ? node == - parent->left ? &parent->left : &parent->right : &tree->root; - - switch(avl_check_balance(node)) { - case -1: - child = node->left; -#ifdef AVL_DEPTH - - if(L_AVL_DEPTH(child) >= R_AVL_DEPTH(child)) { -#else - - if(AVL_L_COUNT(child) >= AVL_R_COUNT(child)) { -#endif - node->left = child->right; - - if(node->left) { - node->left->parent = node; - } - - child->right = node; - node->parent = child; - *superparent = child; - child->parent = parent; -#ifdef AVL_COUNT - node->count = AVL_CALC_COUNT(node); - child->count = AVL_CALC_COUNT(child); -#endif -#ifdef AVL_DEPTH - node->depth = AVL_CALC_DEPTH(node); - child->depth = AVL_CALC_DEPTH(child); -#endif - } else { - gchild = child->right; - node->left = gchild->right; - - if(node->left) { - node->left->parent = node; - } - - child->right = gchild->left; - - if(child->right) { - child->right->parent = child; - } - - gchild->right = node; - - gchild->right->parent = gchild; - gchild->left = child; - - gchild->left->parent = gchild; - - *superparent = gchild; - gchild->parent = parent; -#ifdef AVL_COUNT - node->count = AVL_CALC_COUNT(node); - child->count = AVL_CALC_COUNT(child); - gchild->count = AVL_CALC_COUNT(gchild); -#endif -#ifdef AVL_DEPTH - node->depth = AVL_CALC_DEPTH(node); - child->depth = AVL_CALC_DEPTH(child); - gchild->depth = AVL_CALC_DEPTH(gchild); -#endif - } - - break; - - case 1: - child = node->right; -#ifdef AVL_DEPTH - - if(R_AVL_DEPTH(child) >= L_AVL_DEPTH(child)) { -#else - - if(AVL_R_COUNT(child) >= AVL_L_COUNT(child)) { -#endif - node->right = child->left; - - if(node->right) { - node->right->parent = node; - } - - child->left = node; - node->parent = child; - *superparent = child; - child->parent = parent; -#ifdef AVL_COUNT - node->count = AVL_CALC_COUNT(node); - child->count = AVL_CALC_COUNT(child); -#endif -#ifdef AVL_DEPTH - node->depth = AVL_CALC_DEPTH(node); - child->depth = AVL_CALC_DEPTH(child); -#endif - } else { - gchild = child->left; - node->right = gchild->left; - - if(node->right) { - node->right->parent = node; - } - - child->left = gchild->right; - - if(child->left) { - child->left->parent = child; - } - - gchild->left = node; - - gchild->left->parent = gchild; - gchild->right = child; - - gchild->right->parent = gchild; - - *superparent = gchild; - gchild->parent = parent; -#ifdef AVL_COUNT - node->count = AVL_CALC_COUNT(node); - child->count = AVL_CALC_COUNT(child); - gchild->count = AVL_CALC_COUNT(gchild); -#endif -#ifdef AVL_DEPTH - node->depth = AVL_CALC_DEPTH(node); - child->depth = AVL_CALC_DEPTH(child); - gchild->depth = AVL_CALC_DEPTH(gchild); -#endif - } - - break; - - default: -#ifdef AVL_COUNT - node->count = AVL_CALC_COUNT(node); -#endif -#ifdef AVL_DEPTH - node->depth = AVL_CALC_DEPTH(node); -#endif - } - - node = parent; - } -} - -/* (De)constructors */ - -avl_tree_t *avl_alloc_tree(avl_compare_t compare, avl_action_t delete) { - avl_tree_t *tree; - - tree = xmalloc_and_zero(sizeof(avl_tree_t)); - tree->compare = compare; - tree->delete = delete; - - return tree; -} - -void avl_free_tree(avl_tree_t *tree) { - free(tree); -} - -avl_node_t *avl_alloc_node(void) { - return xmalloc_and_zero(sizeof(avl_node_t)); -} - -void avl_free_node(avl_tree_t *tree, avl_node_t *node) { - if(node->data && tree->delete) { - tree->delete(node->data); - } - - free(node); -} - -/* Searching */ - -void *avl_search(const avl_tree_t *tree, const void *data) { - avl_node_t *node; - - node = avl_search_node(tree, data); - - return node ? node->data : NULL; -} - -void *avl_search_closest(const avl_tree_t *tree, const void *data, int *result) { - avl_node_t *node; - - node = avl_search_closest_node(tree, data, result); - - return node ? node->data : NULL; -} - -void *avl_search_closest_smaller(const avl_tree_t *tree, const void *data) { - avl_node_t *node; - - node = avl_search_closest_smaller_node(tree, data); - - return node ? node->data : NULL; -} - -void *avl_search_closest_greater(const avl_tree_t *tree, const void *data) { - avl_node_t *node; - - node = avl_search_closest_greater_node(tree, data); - - return node ? node->data : NULL; -} - -avl_node_t *avl_search_node(const avl_tree_t *tree, const void *data) { - avl_node_t *node; - int result; - - node = avl_search_closest_node(tree, data, &result); - - return result ? NULL : node; -} - -avl_node_t *avl_search_closest_node(const avl_tree_t *tree, const void *data, - int *result) { - avl_node_t *node; - int c; - - node = tree->root; - - if(!node) { - if(result) { - *result = 0; - } - - return NULL; - } - - for(;;) { - c = tree->compare(data, node->data); - - if(c < 0) { - if(node->left) { - node = node->left; - } else { - if(result) { - *result = -1; - } - - break; - } - } else if(c > 0) { - if(node->right) { - node = node->right; - } else { - if(result) { - *result = 1; - } - - break; - } - } else { - if(result) { - *result = 0; - } - - break; - } - } - - return node; -} - -avl_node_t *avl_search_closest_smaller_node(const avl_tree_t *tree, - const void *data) { - avl_node_t *node; - int result; - - node = avl_search_closest_node(tree, data, &result); - - if(result < 0) { - node = node->prev; - } - - return node; -} - -avl_node_t *avl_search_closest_greater_node(const avl_tree_t *tree, - const void *data) { - avl_node_t *node; - int result; - - node = avl_search_closest_node(tree, data, &result); - - if(result > 0) { - node = node->next; - } - - return node; -} - -/* Insertion and deletion */ - -avl_node_t *avl_insert(avl_tree_t *tree, void *data) { - avl_node_t *closest, *new; - int result; - - if(!tree->root) { - new = avl_alloc_node(); - new->data = data; - avl_insert_top(tree, new); - } else { - closest = avl_search_closest_node(tree, data, &result); - - switch(result) { - case -1: - new = avl_alloc_node(); - new->data = data; - avl_insert_before(tree, closest, new); - break; - - case 1: - new = avl_alloc_node(); - new->data = data; - avl_insert_after(tree, closest, new); - break; - - default: - return NULL; - } - } - -#ifdef AVL_COUNT - new->count = 1; -#endif -#ifdef AVL_DEPTH - new->depth = 1; -#endif - - return new; -} - -avl_node_t *avl_insert_node(avl_tree_t *tree, avl_node_t *node) { - avl_node_t *closest; - int result; - - if(!tree->root) { - avl_insert_top(tree, node); - } else { - closest = avl_search_closest_node(tree, node->data, &result); - - switch(result) { - case -1: - avl_insert_before(tree, closest, node); - break; - - case 1: - avl_insert_after(tree, closest, node); - break; - - case 0: - return NULL; - } - } - -#ifdef AVL_COUNT - node->count = 1; -#endif -#ifdef AVL_DEPTH - node->depth = 1; -#endif - - return node; -} - -void avl_insert_top(avl_tree_t *tree, avl_node_t *node) { - node->prev = node->next = node->parent = NULL; - tree->head = tree->tail = tree->root = node; -} - -void avl_insert_before(avl_tree_t *tree, avl_node_t *before, - avl_node_t *node) { - if(!before) { - if(tree->tail) { - avl_insert_after(tree, tree->tail, node); - } else { - avl_insert_top(tree, node); - } - - return; - } - - node->next = before; - node->parent = before; - node->prev = before->prev; - - if(before->left) { - avl_insert_after(tree, before->prev, node); - return; - } - - if(before->prev) { - before->prev->next = node; - } else { - tree->head = node; - } - - before->prev = node; - before->left = node; - - avl_rebalance(tree, before); -} - -void avl_insert_after(avl_tree_t *tree, avl_node_t *after, avl_node_t *node) { - if(!after) { - if(tree->head) { - avl_insert_before(tree, tree->head, node); - } else { - avl_insert_top(tree, node); - } - - return; - } - - if(after->right) { - avl_insert_before(tree, after->next, node); - return; - } - - node->prev = after; - node->parent = after; - node->next = after->next; - - if(after->next) { - after->next->prev = node; - } else { - tree->tail = node; - } - - after->next = node; - after->right = node; - - avl_rebalance(tree, after); -} - -avl_node_t *avl_unlink(avl_tree_t *tree, void *data) { - avl_node_t *node; - - node = avl_search_node(tree, data); - - if(node) { - avl_unlink_node(tree, node); - } - - return node; -} - -void avl_unlink_node(avl_tree_t *tree, avl_node_t *node) { - avl_node_t *parent; - avl_node_t **superparent; - avl_node_t *subst, *left, *right; - avl_node_t *balnode; - - if(node->prev) { - node->prev->next = node->next; - } else { - tree->head = node->next; - } - - if(node->next) { - node->next->prev = node->prev; - } else { - tree->tail = node->prev; - } - - parent = node->parent; - - superparent = - parent ? node == - parent->left ? &parent->left : &parent->right : &tree->root; - - left = node->left; - right = node->right; - - if(!left) { - *superparent = right; - - if(right) { - right->parent = parent; - } - - balnode = parent; - } else if(!right) { - *superparent = left; - left->parent = parent; - balnode = parent; - } else { - subst = node->prev; - - if(!subst) { // This only happens if node is not actually in a tree at all. - abort(); - } - - if(subst == left) { - balnode = subst; - } else { - balnode = subst->parent; - balnode->right = subst->left; - - if(balnode->right) { - balnode->right->parent = balnode; - } - - subst->left = left; - left->parent = subst; - } - - subst->right = right; - subst->parent = parent; - right->parent = subst; - *superparent = subst; - } - - avl_rebalance(tree, balnode); - - node->next = node->prev = node->parent = node->left = node->right = NULL; - -#ifdef AVL_COUNT - node->count = 0; -#endif -#ifdef AVL_DEPTH - node->depth = 0; -#endif -} - -void avl_delete_node(avl_tree_t *tree, avl_node_t *node) { - avl_unlink_node(tree, node); - avl_free_node(tree, node); -} - -void avl_delete(avl_tree_t *tree, void *data) { - avl_node_t *node; - - node = avl_search_node(tree, data); - - if(node) { - avl_delete_node(tree, node); - } -} - -/* Fast tree cleanup */ - -void avl_delete_tree(avl_tree_t *tree) { - avl_node_t *node, *next; - - for(node = tree->head; node; node = next) { - next = node->next; - avl_free_node(tree, node); - } - - avl_free_tree(tree); -} - -/* Tree walking */ - -void avl_foreach(const avl_tree_t *tree, avl_action_t action) { - avl_node_t *node, *next; - - for(node = tree->head; node; node = next) { - next = node->next; - action(node->data); - } -} - -void avl_foreach_node(const avl_tree_t *tree, avl_action_t action) { - avl_node_t *node, *next; - - for(node = tree->head; node; node = next) { - next = node->next; - action(node); - } -} - -/* Indexing */ - -#ifdef AVL_COUNT -unsigned int avl_count(const avl_tree_t *tree) { - return AVL_NODE_COUNT(tree->root); -} - -avl_node_t *avl_get_node(const avl_tree_t *tree, unsigned int index) { - avl_node_t *node; - unsigned int c; - - node = tree->root; - - while(node) { - c = AVL_L_COUNT(node); - - if(index < c) { - node = node->left; - } else if(index > c) { - node = node->right; - index -= c + 1; - } else { - return node; - } - } - - return NULL; -} - -unsigned int avl_index(const avl_node_t *node) { - avl_node_t *next; - unsigned int index; - - index = AVL_L_COUNT(node); - - while((next = node->parent)) { - if(node == next->right) { - index += AVL_L_COUNT(next) + 1; - } - - node = next; - } - - return index; -} -#endif -#ifdef AVL_DEPTH -unsigned int avl_depth(const avl_tree_t *tree) { - return AVL_NODE_DEPTH(tree->root); -} -#endif diff --git a/src/avl_tree.h b/src/avl_tree.h deleted file mode 100644 index e8cefcf..0000000 --- a/src/avl_tree.h +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef TINC_AVL_TREE_H -#define TINC_AVL_TREE_H - -/* - avl_tree.h -- header file for avl_tree.c - Copyright (C) 1998 Michael H. Buselli - 2000-2005 Ivo Timmermans, - 2000-2006 Guus Sliepen - 2000-2005 Wessel Dankers - - 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. - - Original AVL tree library by Michael H. Buselli . - - Modified 2000-11-28 by Wessel Dankers to use counts - instead of depths, to add the ->next and ->prev and to generally obfuscate - the code. Mail me if you found a bug. - - Cleaned up and incorporated some of the ideas from the red-black tree - library for inclusion into tinc (https://www.tinc-vpn.org/) by - Guus Sliepen . -*/ - -#ifndef AVL_DEPTH -#ifndef AVL_COUNT -#define AVL_DEPTH -#endif -#endif - -typedef struct avl_node_t { - - /* Linked list part */ - - struct avl_node_t *next; - struct avl_node_t *prev; - - /* Tree part */ - - struct avl_node_t *parent; - struct avl_node_t *left; - struct avl_node_t *right; - -#ifdef AVL_COUNT - unsigned int count; -#endif -#ifdef AVL_DEPTH - unsigned char depth; -#endif - - /* Payload */ - - void *data; - -} avl_node_t; - -typedef int (*avl_compare_t)(const void *data1, const void *data2); -typedef void (*avl_action_t)(const void *data); -typedef void (*avl_action_node_t)(const avl_node_t *node); - -typedef struct avl_tree_t { - - /* Linked list part */ - - avl_node_t *head; - avl_node_t *tail; - - /* Tree part */ - - avl_node_t *root; - - avl_compare_t compare; - avl_action_t delete; - -} avl_tree_t; - -/* (De)constructors */ - -extern avl_tree_t *avl_alloc_tree(avl_compare_t compare, avl_action_t delete); -extern void avl_free_tree(avl_tree_t *tree); - -extern avl_node_t *avl_alloc_node(void); -extern void avl_free_node(avl_tree_t *tree, avl_node_t *node); - -/* Insertion and deletion */ - -extern avl_node_t *avl_insert(avl_tree_t *tree, void *data); -extern avl_node_t *avl_insert_node(avl_tree_t *tree, avl_node_t *node); - -extern void avl_insert_top(avl_tree_t *tree, avl_node_t *node); -extern void avl_insert_before(avl_tree_t *tree, avl_node_t *before, avl_node_t *node); -extern void avl_insert_after(avl_tree_t *tree, avl_node_t *after, avl_node_t *node); - -extern avl_node_t *avl_unlink(avl_tree_t *tree, void *data); -extern void avl_unlink_node(avl_tree_t *tree, avl_node_t *node); -extern void avl_delete(avl_tree_t *tree, void *data); -extern void avl_delete_node(avl_tree_t *tree, avl_node_t *node); - -/* Fast tree cleanup */ - -extern void avl_delete_tree(avl_tree_t *tree); - -/* Searching */ - -extern void *avl_search(const avl_tree_t *tree, const void *data); -extern void *avl_search_closest(const avl_tree_t *tree, const void *data, int *result); -extern void *avl_search_closest_smaller(const avl_tree_t *tree, const void *data); -extern void *avl_search_closest_greater(const avl_tree_t *tree, const void *data); - -extern avl_node_t *avl_search_node(const avl_tree_t *tree, const void *data); -extern avl_node_t *avl_search_closest_node(const avl_tree_t *tree, const void *data, int *result); -extern avl_node_t *avl_search_closest_smaller_node(const avl_tree_t *tree, const void *data); -extern avl_node_t *avl_search_closest_greater_node(const avl_tree_t *tree, const void *data); - -/* Tree walking */ - -extern void avl_foreach(const avl_tree_t *tree, avl_action_t action); -extern void avl_foreach_node(const avl_tree_t *tree, avl_action_t action); - -/* Indexing */ - -#ifdef AVL_COUNT -extern unsigned int avl_count(const avl_tree_t *tree); -extern avl_node_t *avl_get_node(const avl_tree_t *tree, unsigned int index); -extern unsigned int avl_index(const avl_node_t *node); -#endif -#ifdef AVL_DEPTH -extern unsigned int avl_depth(const avl_tree_t *tree); -#endif - -#endif diff --git a/src/bsd/device.c b/src/bsd/device.c index 23d6d69..235f4ec 100644 --- a/src/bsd/device.c +++ b/src/bsd/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction BSD tun/tap device Copyright (C) 2001-2005 Ivo Timmermans, - 2001-2016 Guus Sliepen + 2001-2021 Guus Sliepen 2009 Grzegorz Dymarek This program is free software; you can redistribute it and/or modify @@ -24,13 +24,14 @@ #include "../conf.h" #include "../device.h" #include "../logger.h" +#include "../names.h" #include "../net.h" #include "../route.h" #include "../utils.h" #include "../xalloc.h" #ifdef ENABLE_TUNEMU -#include "tunemu.h" +#include "bsd/tunemu.h" #endif #ifdef HAVE_NET_IF_UTUN_H @@ -39,8 +40,13 @@ #include #endif +#if defined(HAVE_FREEBSD) || defined(HAVE_DRAGONFLY) +#define DEFAULT_TUN_DEVICE "/dev/tun" // Use the autoclone device +#define DEFAULT_TAP_DEVICE "/dev/tap" +#else #define DEFAULT_TUN_DEVICE "/dev/tun0" #define DEFAULT_TAP_DEVICE "/dev/tap0" +#endif typedef enum device_type { DEVICE_TYPE_TUN, @@ -56,8 +62,6 @@ int device_fd = -1; char *device = NULL; char *iface = NULL; static const char *device_info = "OS X utun device"; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; #if defined(ENABLE_TUNEMU) static device_type_t device_type = DEVICE_TYPE_TUNEMU; #elif defined(HAVE_OPENBSD) || defined(HAVE_FREEBSD) || defined(HAVE_DRAGONFLY) @@ -71,7 +75,7 @@ static bool setup_utun(void) { device_fd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); if(device_fd == -1) { - logger(LOG_ERR, "Could not open PF_SYSTEM socket: %s\n", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open PF_SYSTEM socket: %s\n", strerror(errno)); return false; } @@ -80,7 +84,7 @@ static bool setup_utun(void) { strlcpy(info.ctl_name, UTUN_CONTROL_NAME, sizeof(info.ctl_name)); if(ioctl(device_fd, CTLIOCGINFO, &info) == -1) { - logger(LOG_ERR, "ioctl(CTLIOCGINFO) failed: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "ioctl(CTLIOCGINFO) failed: %s", strerror(errno)); return false; } @@ -104,7 +108,7 @@ static bool setup_utun(void) { }; if(connect(device_fd, (struct sockaddr *)&sc, sizeof(sc)) == -1) { - logger(LOG_ERR, "Could not connect utun socket: %s\n", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not connect utun socket: %s\n", strerror(errno)); return false; } @@ -117,22 +121,14 @@ static bool setup_utun(void) { iface = xstrdup(name); } - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } #endif static bool setup_device(void) { - // Find out which device file to open - - if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { - if(routing_mode == RMODE_ROUTER) { - device = xstrdup(DEFAULT_TUN_DEVICE); - } else { - device = xstrdup(DEFAULT_TAP_DEVICE); - } - } + get_config_string(lookup_config(config_tree, "Device"), &device); // Find out if it's supposed to be a tun or a tap device @@ -161,26 +157,36 @@ static bool setup_device(void) { } else if(!strcasecmp(type, "tap")) { device_type = DEVICE_TYPE_TAP; } else { - logger(LOG_ERR, "Unknown device type %s!", type); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown device type %s!", type); return false; } } else { #ifdef HAVE_NET_IF_UTUN_H - if(strncmp(device, "utun", 4) == 0 || strncmp(device, "/dev/utun", 9) == 0) { + if(device && (strncmp(device, "utun", 4) == 0 || strncmp(device, "/dev/utun", 9) == 0)) { device_type = DEVICE_TYPE_UTUN; } else #endif - if(strstr(device, "tap") || routing_mode != RMODE_ROUTER) { + if((device && strstr(device, "tap")) || routing_mode != RMODE_ROUTER) { device_type = DEVICE_TYPE_TAP; } } if(routing_mode == RMODE_SWITCH && device_type != DEVICE_TYPE_TAP) { - logger(LOG_ERR, "Only tap devices support switch mode!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Only tap devices support switch mode!"); return false; } + // Find out which device file to open + + if(!device) { + if(device_type == DEVICE_TYPE_TAP) { + device = xstrdup(DEFAULT_TAP_DEVICE); + } else { + device = xstrdup(DEFAULT_TUN_DEVICE); + } + } + // Open the device switch(device_type) { @@ -203,7 +209,7 @@ static bool setup_device(void) { } if(device_fd < 0) { - logger(LOG_ERR, "Could not open %s: %s", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device, strerror(errno)); return false; } @@ -233,7 +239,7 @@ static bool setup_device(void) { if(!get_config_string(lookup_config(config_tree, "Interface"), &iface)) { iface = xstrdup(strrchr(realname, '/') ? strrchr(realname, '/') + 1 : realname); } else if(strcmp(iface, strrchr(realname, '/') ? strrchr(realname, '/') + 1 : realname)) { - logger(LOG_WARNING, "Warning: Interface does not match Device. $INTERFACE might be set incorrectly."); + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: Interface does not match Device. $INTERFACE might be set incorrectly."); } // Configure the device as best as we can @@ -248,7 +254,7 @@ static bool setup_device(void) { const int zero = 0; if(ioctl(device_fd, TUNSIFHEAD, &zero, sizeof(zero)) == -1) { - logger(LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); return false; } } @@ -270,7 +276,7 @@ static bool setup_device(void) { const int one = 1; if(ioctl(device_fd, TUNSIFHEAD, &one, sizeof(one)) == -1) { - logger(LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); return false; } } @@ -297,10 +303,7 @@ static bool setup_device(void) { struct ifreq ifr; if(ioctl(device_fd, TAPGIFNAME, (void *)&ifr) == 0) { - if(iface) { - free(iface); - } - + free(iface); iface = xstrdup(ifr.ifr_name); } } @@ -323,7 +326,7 @@ static bool setup_device(void) { #endif - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } @@ -341,112 +344,115 @@ static void close_device(void) { close(device_fd); } + device_fd = -1; + free(device); + device = NULL; free(iface); + iface = NULL; + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { - int lenin; + int inlen; switch(device_type) { case DEVICE_TYPE_TUN: #ifdef ENABLE_TUNEMU case DEVICE_TYPE_TUNEMU: if(device_type == DEVICE_TYPE_TUNEMU) { - lenin = tunemu_read(device_fd, packet->data + 14, MTU - 14); + inlen = tunemu_read(device_fd, DATA(packet) + 14, MTU - 14); } else #endif - lenin = read(device_fd, packet->data + 14, MTU - 14); + inlen = read(device_fd, DATA(packet) + 14, MTU - 14); - if(lenin <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + if(inlen <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - switch(packet->data[14] >> 4) { + switch(DATA(packet)[14] >> 4) { case 4: - packet->data[12] = 0x08; - packet->data[13] = 0x00; + DATA(packet)[12] = 0x08; + DATA(packet)[13] = 0x00; break; case 6: - packet->data[12] = 0x86; - packet->data[13] = 0xDD; + DATA(packet)[12] = 0x86; + DATA(packet)[13] = 0xDD; break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, - "Unknown IP version %d while reading packet from %s %s", - packet->data[14] >> 4, device_info, device); + logger(DEBUG_TRAFFIC, LOG_ERR, + "Unknown IP version %d while reading packet from %s %s", + DATA(packet)[14] >> 4, device_info, device); return false; } - memset(packet->data, 0, 12); - packet->len = lenin + 14; + memset(DATA(packet), 0, 12); + packet->len = inlen + 14; break; case DEVICE_TYPE_UTUN: case DEVICE_TYPE_TUNIFHEAD: { - if((lenin = read(device_fd, packet->data + 10, MTU - 10)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + if((inlen = read(device_fd, DATA(packet) + 10, MTU - 10)) <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - switch(packet->data[14] >> 4) { + switch(DATA(packet)[14] >> 4) { case 4: - packet->data[12] = 0x08; - packet->data[13] = 0x00; + DATA(packet)[12] = 0x08; + DATA(packet)[13] = 0x00; break; case 6: - packet->data[12] = 0x86; - packet->data[13] = 0xDD; + DATA(packet)[12] = 0x86; + DATA(packet)[13] = 0xDD; break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, - "Unknown IP version %d while reading packet from %s %s", - packet->data[14] >> 4, device_info, device); + logger(DEBUG_TRAFFIC, LOG_ERR, + "Unknown IP version %d while reading packet from %s %s", + DATA(packet)[14] >> 4, device_info, device); return false; } - memset(packet->data, 0, 12); - packet->len = lenin + 10; + memset(DATA(packet), 0, 12); + packet->len = inlen + 10; break; } case DEVICE_TYPE_TAP: - if((lenin = read(device_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + if((inlen = read(device_fd, DATA(packet), MTU)) <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - packet->len = lenin; + packet->len = inlen; break; default: return false; } - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", + packet->len, device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); switch(device_type) { case DEVICE_TYPE_TUN: - if(write(device_fd, packet->data + 14, packet->len - 14) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + if(write(device_fd, DATA(packet) + 14, packet->len - 14) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -455,7 +461,7 @@ static bool write_packet(vpn_packet_t *packet) { case DEVICE_TYPE_UTUN: case DEVICE_TYPE_TUNIFHEAD: { - int af = (packet->data[12] << 8) + packet->data[13]; + int af = (DATA(packet)[12] << 8) + DATA(packet)[13]; uint32_t type; switch(af) { @@ -468,16 +474,16 @@ static bool write_packet(vpn_packet_t *packet) { break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, - "Unknown address family %x while writing packet to %s %s", - af, device_info, device); + logger(DEBUG_TRAFFIC, LOG_ERR, + "Unknown address family %x while writing packet to %s %s", + af, device_info, device); return false; } - memcpy(packet->data + 10, &type, sizeof(type)); + memcpy(DATA(packet) + 10, &type, sizeof(type)); - if(write(device_fd, packet->data + 10, packet->len - 10) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + if(write(device_fd, DATA(packet) + 10, packet->len - 10) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -486,8 +492,8 @@ static bool write_packet(vpn_packet_t *packet) { } case DEVICE_TYPE_TAP: - if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + if(write(device_fd, DATA(packet), packet->len) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -497,8 +503,8 @@ static bool write_packet(vpn_packet_t *packet) { #ifdef ENABLE_TUNEMU case DEVICE_TYPE_TUNEMU: - if(tunemu_write(device_fd, packet->data + 14, packet->len - 14) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + if(tunemu_write(device_fd, DATA(packet) + 14, packet->len - 14) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -510,21 +516,12 @@ static bool write_packet(vpn_packet_t *packet) { return false; } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t os_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/bsd/tunemu.c b/src/bsd/tunemu.c index 7ff6f72..2248264 100644 --- a/src/bsd/tunemu.c +++ b/src/bsd/tunemu.c @@ -45,9 +45,9 @@ #define PPPIOCSFLAGS _IOW('t', 89, int) #define PPPIOCSNPMODE _IOW('t', 75, struct npioctl) #define PPPIOCATTCHAN _IOW('t', 56, int) -#define PPPIOCGCHAN _IOR('t', 55, int) +#define PPPIOCGCHAN _IOR('t', 55, int) #define PPPIOCCONNECT _IOW('t', 58, int) -#define PPPIOCGUNIT _IOR('t', 86, int) +#define PPPIOCGUNIT _IOR('t', 86, int) struct sockaddr_ppp { u_int8_t ppp_len; @@ -83,7 +83,7 @@ static char *data_buffer = NULL; static void tun_error(char *format, ...) { va_list vl; va_start(vl, format); - vsnprintf(tunemu_error, ERROR_BUFFER_SIZE, format, vl); + vsnprintf(tunemu_error, sizeof(tunemu_error), format, vl); va_end(vl); } diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..60adec8 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,110 @@ +/* + buffer.c -- buffer management + Copyright (C) 2011 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 "buffer.h" +#include "xalloc.h" + +void buffer_compact(buffer_t *buffer, uint32_t maxsize) { + if(buffer->len >= maxsize || buffer->offset / 7 > buffer->len / 8) { + memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset); + buffer->len -= buffer->offset; + buffer->offset = 0; + } +} + +// Make sure we can add size bytes to the buffer, and return a pointer to the start of those bytes. + +char *buffer_prepare(buffer_t *buffer, uint32_t size) { + if(!buffer->data) { + buffer->maxlen = size; + buffer->data = xmalloc(size); + } else { + if(buffer->offset && buffer->len + size > buffer->maxlen) { + memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset); + buffer->len -= buffer->offset; + buffer->offset = 0; + } + + if(buffer->len + size > buffer->maxlen) { + buffer->maxlen = buffer->len + size; + buffer->data = xrealloc(buffer->data, buffer->maxlen); + } + } + + char *start = buffer->data + buffer->len; + + buffer->len += size; + + return start; +} + +// Copy data into the buffer. + +void buffer_add(buffer_t *buffer, const char *data, uint32_t size) { + memcpy(buffer_prepare(buffer, size), data, size); +} + +// Remove given number of bytes from the buffer, return a pointer to the start of them. + +static char *buffer_consume(buffer_t *buffer, uint32_t size) { + char *start = buffer->data + buffer->offset; + + buffer->offset += size; + + if(buffer->offset >= buffer->len) { + buffer->offset = 0; + buffer->len = 0; + } + + return start; +} + +// Check if there is a complete line in the buffer, and if so, return it NULL-terminated. + +char *buffer_readline(buffer_t *buffer) { + char *newline = memchr(buffer->data + buffer->offset, '\n', buffer->len - buffer->offset); + + if(!newline) { + return NULL; + } + + uint32_t len = newline + 1 - (buffer->data + buffer->offset); + *newline = 0; + return buffer_consume(buffer, len); +} + +// Check if we have enough bytes in the buffer, and if so, return a pointer to the start of them. + +char *buffer_read(buffer_t *buffer, uint32_t size) { + if(buffer->len - buffer->offset < size) { + return NULL; + } + + return buffer_consume(buffer, size); +} + +void buffer_clear(buffer_t *buffer) { + free(buffer->data); + buffer->data = NULL; + buffer->maxlen = 0; + buffer->len = 0; + buffer->offset = 0; +} diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..b25c8ad --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,18 @@ +#ifndef TINC_BUFFER_H +#define TINC_BUFFER_H + +typedef struct buffer_t { + char *data; + uint32_t maxlen; + uint32_t len; + uint32_t offset; +} buffer_t; + +extern void buffer_compact(buffer_t *buffer, uint32_t maxsize); +extern char *buffer_prepare(buffer_t *buffer, uint32_t size); +extern void buffer_add(buffer_t *buffer, const char *data, uint32_t size); +extern char *buffer_readline(buffer_t *buffer); +extern char *buffer_read(buffer_t *buffer, uint32_t size); +extern void buffer_clear(buffer_t *buffer); + +#endif diff --git a/src/chacha-poly1305/chacha-poly1305.c b/src/chacha-poly1305/chacha-poly1305.c new file mode 100644 index 0000000..a74c502 --- /dev/null +++ b/src/chacha-poly1305/chacha-poly1305.c @@ -0,0 +1,106 @@ +#include "../system.h" + +#include "../cipher.h" +#include "../xalloc.h" + +#include "chacha.h" +#include "chacha-poly1305.h" +#include "poly1305.h" + +struct chacha_poly1305_ctx { + struct chacha_ctx main_ctx, header_ctx; +}; + +chacha_poly1305_ctx_t *chacha_poly1305_init(void) { + chacha_poly1305_ctx_t *ctx = xzalloc(sizeof(*ctx)); + return ctx; +} + +void chacha_poly1305_exit(chacha_poly1305_ctx_t *ctx) { + free(ctx); +} + +bool chacha_poly1305_set_key(chacha_poly1305_ctx_t *ctx, const void *vkey) { + const uint8_t *key = vkey; + chacha_keysetup(&ctx->main_ctx, key, 256); + chacha_keysetup(&ctx->header_ctx, key + 32, 256); + return true; +} + +static void put_u64(void *vp, uint64_t v) { + uint8_t *p = (uint8_t *) vp; + + p[0] = (uint8_t)(v >> 56) & 0xff; + p[1] = (uint8_t)(v >> 48) & 0xff; + p[2] = (uint8_t)(v >> 40) & 0xff; + p[3] = (uint8_t)(v >> 32) & 0xff; + p[4] = (uint8_t)(v >> 24) & 0xff; + p[5] = (uint8_t)(v >> 16) & 0xff; + p[6] = (uint8_t)(v >> 8) & 0xff; + p[7] = (uint8_t) v & 0xff; +} + +bool chacha_poly1305_encrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *voutdata, size_t *outlen) { + uint8_t seqbuf[8]; + const uint8_t one[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; /* NB little-endian */ + uint8_t poly_key[POLY1305_KEYLEN]; + uint8_t *outdata = voutdata; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + put_u64(seqbuf, seqnr); + chacha_ivsetup(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup(&ctx->main_ctx, seqbuf, one); + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + poly1305_auth(outdata + inlen, outdata, inlen, poly_key); + + if(outlen) { + *outlen = inlen + POLY1305_TAGLEN; + } + + return true; +} + +bool chacha_poly1305_decrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *vindata, size_t inlen, void *outdata, size_t *outlen) { + uint8_t seqbuf[8]; + const uint8_t one[8] = { 1, 0, 0, 0, 0, 0, 0, 0 }; /* NB little-endian */ + uint8_t expected_tag[POLY1305_TAGLEN], poly_key[POLY1305_KEYLEN]; + const uint8_t *indata = vindata; + + /* + * Run ChaCha20 once to generate the Poly1305 key. The IV is the + * packet sequence number. + */ + memset(poly_key, 0, sizeof(poly_key)); + put_u64(seqbuf, seqnr); + chacha_ivsetup(&ctx->main_ctx, seqbuf, NULL); + chacha_encrypt_bytes(&ctx->main_ctx, poly_key, poly_key, sizeof(poly_key)); + + /* Set Chacha's block counter to 1 */ + chacha_ivsetup(&ctx->main_ctx, seqbuf, one); + + /* Check tag before anything else */ + inlen -= POLY1305_TAGLEN; + const uint8_t *tag = indata + inlen; + + poly1305_auth(expected_tag, indata, inlen, poly_key); + + if(memcmp(expected_tag, tag, POLY1305_TAGLEN)) { + return false; + } + + chacha_encrypt_bytes(&ctx->main_ctx, indata, outdata, inlen); + + if(outlen) { + *outlen = inlen; + } + + return true; +} diff --git a/src/chacha-poly1305/chacha-poly1305.h b/src/chacha-poly1305/chacha-poly1305.h new file mode 100644 index 0000000..af7eaf5 --- /dev/null +++ b/src/chacha-poly1305/chacha-poly1305.h @@ -0,0 +1,15 @@ +#ifndef CHACHA_POLY1305_H +#define CHACHA_POLY1305_H + +#define CHACHA_POLY1305_KEYLEN 64 + +typedef struct chacha_poly1305_ctx chacha_poly1305_ctx_t; + +extern chacha_poly1305_ctx_t *chacha_poly1305_init(void); +extern void chacha_poly1305_exit(chacha_poly1305_ctx_t *); +extern bool chacha_poly1305_set_key(chacha_poly1305_ctx_t *ctx, const void *key); + +extern bool chacha_poly1305_encrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen); +extern bool chacha_poly1305_decrypt(chacha_poly1305_ctx_t *ctx, uint64_t seqnr, const void *indata, size_t inlen, void *outdata, size_t *outlen); + +#endif //CHACHA_POLY1305_H diff --git a/src/chacha-poly1305/chacha.c b/src/chacha-poly1305/chacha.c new file mode 100644 index 0000000..696f44a --- /dev/null +++ b/src/chacha-poly1305/chacha.c @@ -0,0 +1,224 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#include "../system.h" + +#include "chacha.h" + +typedef struct chacha_ctx chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((uint8_t)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void chacha_keysetup(chacha_ctx *x, const uint8_t *k, uint32_t kbits) { + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + + if(kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void chacha_ivsetup(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) { + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void +chacha_encrypt_bytes(chacha_ctx *x, const uint8_t *m, uint8_t *c, uint32_t bytes) { + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t *ctarget = NULL; + uint8_t tmp[64]; + uint32_t i; + + if(!bytes) { + return; + } + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for(;;) { + if(bytes < 64) { + for(i = 0; i < bytes; ++i) { + tmp[i] = m[i]; + } + + m = tmp; + ctarget = c; + c = tmp; + } + + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + + for(i = 20; i > 0; i -= 2) { + QUARTERROUND(x0, x4, x8, x12) + QUARTERROUND(x1, x5, x9, x13) + QUARTERROUND(x2, x6, x10, x14) + QUARTERROUND(x3, x7, x11, x15) + QUARTERROUND(x0, x5, x10, x15) + QUARTERROUND(x1, x6, x11, x12) + QUARTERROUND(x2, x7, x8, x13) + QUARTERROUND(x3, x4, x9, x14) + } + + x0 = PLUS(x0, j0); + x1 = PLUS(x1, j1); + x2 = PLUS(x2, j2); + x3 = PLUS(x3, j3); + x4 = PLUS(x4, j4); + x5 = PLUS(x5, j5); + x6 = PLUS(x6, j6); + x7 = PLUS(x7, j7); + x8 = PLUS(x8, j8); + x9 = PLUS(x9, j9); + x10 = PLUS(x10, j10); + x11 = PLUS(x11, j11); + x12 = PLUS(x12, j12); + x13 = PLUS(x13, j13); + x14 = PLUS(x14, j14); + x15 = PLUS(x15, j15); + + x0 = XOR(x0, U8TO32_LITTLE(m + 0)); + x1 = XOR(x1, U8TO32_LITTLE(m + 4)); + x2 = XOR(x2, U8TO32_LITTLE(m + 8)); + x3 = XOR(x3, U8TO32_LITTLE(m + 12)); + x4 = XOR(x4, U8TO32_LITTLE(m + 16)); + x5 = XOR(x5, U8TO32_LITTLE(m + 20)); + x6 = XOR(x6, U8TO32_LITTLE(m + 24)); + x7 = XOR(x7, U8TO32_LITTLE(m + 28)); + x8 = XOR(x8, U8TO32_LITTLE(m + 32)); + x9 = XOR(x9, U8TO32_LITTLE(m + 36)); + x10 = XOR(x10, U8TO32_LITTLE(m + 40)); + x11 = XOR(x11, U8TO32_LITTLE(m + 44)); + x12 = XOR(x12, U8TO32_LITTLE(m + 48)); + x13 = XOR(x13, U8TO32_LITTLE(m + 52)); + x14 = XOR(x14, U8TO32_LITTLE(m + 56)); + x15 = XOR(x15, U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + + if(!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0, x0); + U32TO8_LITTLE(c + 4, x1); + U32TO8_LITTLE(c + 8, x2); + U32TO8_LITTLE(c + 12, x3); + U32TO8_LITTLE(c + 16, x4); + U32TO8_LITTLE(c + 20, x5); + U32TO8_LITTLE(c + 24, x6); + U32TO8_LITTLE(c + 28, x7); + U32TO8_LITTLE(c + 32, x8); + U32TO8_LITTLE(c + 36, x9); + U32TO8_LITTLE(c + 40, x10); + U32TO8_LITTLE(c + 44, x11); + U32TO8_LITTLE(c + 48, x12); + U32TO8_LITTLE(c + 52, x13); + U32TO8_LITTLE(c + 56, x14); + U32TO8_LITTLE(c + 60, x15); + + if(bytes <= 64) { + if(bytes < 64) { + for(i = 0; i < bytes; ++i) { + ctarget[i] = c[i]; + } + } + + x->input[12] = j12; + x->input[13] = j13; + return; + } + + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/src/chacha-poly1305/chacha.h b/src/chacha-poly1305/chacha.h new file mode 100644 index 0000000..103c3d8 --- /dev/null +++ b/src/chacha-poly1305/chacha.h @@ -0,0 +1,24 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) +#define CHACHA_BLOCKLEN 64 + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits); +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr); +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, uint8_t *c, uint32_t bytes); + +#endif /* CHACHA_H */ diff --git a/src/chacha-poly1305/poly1305.c b/src/chacha-poly1305/poly1305.c new file mode 100644 index 0000000..4d99b8c --- /dev/null +++ b/src/chacha-poly1305/poly1305.c @@ -0,0 +1,205 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#include "../system.h" + +#include "poly1305.h" + +#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LE(p, v) \ + do { \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); \ + } while (0) + +void +poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { + uint32_t t0, t1, t2, t3; + uint32_t h0, h1, h2, h3, h4; + uint32_t r0, r1, r2, r3, r4; + uint32_t s1, s2, s3, s4; + uint32_t b, nb; + size_t j; + uint64_t t[5]; + uint64_t f0, f1, f2, f3; + uint32_t g0, g1, g2, g3, g4; + uint64_t c; + unsigned char mp[16]; + + /* clamp key */ + t0 = U8TO32_LE(key + 0); + t1 = U8TO32_LE(key + 4); + t2 = U8TO32_LE(key + 8); + t3 = U8TO32_LE(key + 12); + + /* precompute multipliers */ + r0 = t0 & 0x3ffffff; + t0 >>= 26; + t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; + t1 >>= 20; + t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; + t2 >>= 14; + t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; + t3 >>= 8; + r4 = t3 & 0x00fffff; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + /* init state */ + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + + /* full blocks */ + if(inlen < 16) { + goto poly1305_donna_atmost15bytes; + } + +poly1305_donna_16bytes: + m += 16; + inlen -= 16; + + t0 = U8TO32_LE(m - 16); + t1 = U8TO32_LE(m - 12); + t2 = U8TO32_LE(m - 8); + t3 = U8TO32_LE(m - 4); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t) t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t) t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t) t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8) | (1 << 24); + +poly1305_donna_mul: + t[0] = mul32x32_64(h0, r0) + mul32x32_64(h1, s4) + mul32x32_64(h2, s3) + mul32x32_64(h3, s2) + mul32x32_64(h4, s1); + t[1] = mul32x32_64(h0, r1) + mul32x32_64(h1, r0) + mul32x32_64(h2, s4) + mul32x32_64(h3, s3) + mul32x32_64(h4, s2); + t[2] = mul32x32_64(h0, r2) + mul32x32_64(h1, r1) + mul32x32_64(h2, r0) + mul32x32_64(h3, s4) + mul32x32_64(h4, s3); + t[3] = mul32x32_64(h0, r3) + mul32x32_64(h1, r2) + mul32x32_64(h2, r1) + mul32x32_64(h3, r0) + mul32x32_64(h4, s4); + t[4] = mul32x32_64(h0, r4) + mul32x32_64(h1, r3) + mul32x32_64(h2, r2) + mul32x32_64(h3, r1) + mul32x32_64(h4, r0); + + h0 = (uint32_t) t[0] & 0x3ffffff; + c = (t[0] >> 26); + t[1] += c; + h1 = (uint32_t) t[1] & 0x3ffffff; + b = (uint32_t)(t[1] >> 26); + t[2] += b; + h2 = (uint32_t) t[2] & 0x3ffffff; + b = (uint32_t)(t[2] >> 26); + t[3] += b; + h3 = (uint32_t) t[3] & 0x3ffffff; + b = (uint32_t)(t[3] >> 26); + t[4] += b; + h4 = (uint32_t) t[4] & 0x3ffffff; + b = (uint32_t)(t[4] >> 26); + h0 += b * 5; + + if(inlen >= 16) { + goto poly1305_donna_16bytes; + } + + /* final bytes */ +poly1305_donna_atmost15bytes: + + if(!inlen) { + goto poly1305_donna_finish; + } + + for(j = 0; j < inlen; j++) { + mp[j] = m[j]; + } + + mp[j++] = 1; + + for(; j < 16; j++) { + mp[j] = 0; + } + + inlen = 0; + + t0 = U8TO32_LE(mp + 0); + t1 = U8TO32_LE(mp + 4); + t2 = U8TO32_LE(mp + 8); + t3 = U8TO32_LE(mp + 12); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t) t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t) t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t) t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8); + + goto poly1305_donna_mul; + +poly1305_donna_finish: + b = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += b; + b = h1 >> 26; + h1 = h1 & 0x3ffffff; + h2 += b; + b = h2 >> 26; + h2 = h2 & 0x3ffffff; + h3 += b; + b = h3 >> 26; + h3 = h3 & 0x3ffffff; + h4 += b; + b = h4 >> 26; + h4 = h4 & 0x3ffffff; + h0 += b * 5; + b = h0 >> 26; + h0 = h0 & 0x3ffffff; + h1 += b; + + g0 = h0 + 5; + b = g0 >> 26; + g0 &= 0x3ffffff; + g1 = h1 + b; + b = g1 >> 26; + g1 &= 0x3ffffff; + g2 = h2 + b; + b = g2 >> 26; + g2 &= 0x3ffffff; + g3 = h3 + b; + b = g3 >> 26; + g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0) | (h1 << 26)) + (uint64_t) U8TO32_LE(&key[16]); + f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t) U8TO32_LE(&key[20]); + f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t) U8TO32_LE(&key[24]); + f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t) U8TO32_LE(&key[28]); + + U32TO8_LE(&out[0], f0); + f1 += (f0 >> 32); + U32TO8_LE(&out[4], f1); + f2 += (f1 >> 32); + U32TO8_LE(&out[8], f2); + f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); +} diff --git a/src/chacha-poly1305/poly1305.h b/src/chacha-poly1305/poly1305.h new file mode 100644 index 0000000..4ece415 --- /dev/null +++ b/src/chacha-poly1305/poly1305.h @@ -0,0 +1,16 @@ +/* $OpenBSD: poly1305.h,v 1.2 2013/12/19 22:57:13 djm Exp $ */ + +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H + +#define POLY1305_KEYLEN 32 +#define POLY1305_TAGLEN 16 + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, const uint8_t key[POLY1305_KEYLEN]); + +#endif /* POLY1305_H */ diff --git a/src/cipher.h b/src/cipher.h new file mode 100644 index 0000000..2845be8 --- /dev/null +++ b/src/cipher.h @@ -0,0 +1,46 @@ +#ifndef TINC_CIPHER_H +#define TINC_CIPHER_H + +/* + cipher.h -- header file cipher.c + Copyright (C) 2007-2016 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. +*/ + +#define CIPHER_MAX_BLOCK_SIZE 32 +#define CIPHER_MAX_IV_SIZE 16 +#define CIPHER_MAX_KEY_SIZE 32 + +#ifndef DISABLE_LEGACY + +typedef struct cipher cipher_t; + +extern cipher_t *cipher_open_by_name(const char *name) __attribute__((__malloc__)); +extern cipher_t *cipher_open_by_nid(int nid) __attribute__((__malloc__)); +extern void cipher_close(cipher_t *cipher); +extern size_t cipher_keylength(const cipher_t *cipher); +extern size_t cipher_blocksize(const cipher_t *cipher); +extern uint64_t cipher_budget(const cipher_t *cipher); +extern bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) __attribute__((__warn_unused_result__)); +extern bool cipher_set_key_from_rsa(cipher_t *cipher, void *rsa, size_t len, bool encrypt) __attribute__((__warn_unused_result__)); +extern bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) __attribute__((__warn_unused_result__)); +extern bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) __attribute__((__warn_unused_result__)); +extern int cipher_get_nid(const cipher_t *cipher); +extern bool cipher_active(const cipher_t *cipher); + +#endif + +#endif diff --git a/src/conf.c b/src/conf.c index 3f81877..ff592a8 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1,10 +1,11 @@ /* conf.c -- configuration code - Copyright (C) 1998 Robert van der Meulen + Copyright (C) 1998 Robert van der Meulen 1998-2005 Ivo Timmermans - 2000-2014 Guus Sliepen + 2000 Cris van Pelt 2010-2011 Julien Muchembled - 2000 Cris van Pelt + 2000-2021 Guus Sliepen + 2013 Florent Clairambault 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 @@ -23,25 +24,23 @@ #include "system.h" -#include "avl_tree.h" +#include "splay_tree.h" #include "connection.h" #include "conf.h" #include "list.h" #include "logger.h" -#include "netutl.h" /* for str2address */ +#include "names.h" +#include "netutl.h" /* for str2address */ #include "protocol.h" -#include "utils.h" /* for cp */ +#include "utils.h" /* for cp */ #include "xalloc.h" -avl_tree_t *config_tree; +splay_tree_t *config_tree; -int pinginterval = 0; /* seconds between pings */ -int pingtimeout = 0; /* seconds to wait for response */ -char *confbase = NULL; /* directory in which all config files are */ -char *netname = NULL; /* name of the vpn network */ +int pinginterval = 0; /* seconds between pings */ +int pingtimeout = 0; /* seconds to wait for response */ list_t *cmdline_conf = NULL; /* global/host configuration values given at the command line */ - static int config_compare(const config_t *a, const config_t *b) { int result; @@ -67,17 +66,17 @@ static int config_compare(const config_t *a, const config_t *b) { } } -void init_configuration(avl_tree_t **config_tree) { - *config_tree = avl_alloc_tree((avl_compare_t) config_compare, (avl_action_t) free_config); +void init_configuration(splay_tree_t **config_tree) { + *config_tree = splay_alloc_tree((splay_compare_t) config_compare, (splay_action_t) free_config); } -void exit_configuration(avl_tree_t **config_tree) { - avl_delete_tree(*config_tree); +void exit_configuration(splay_tree_t **config_tree) { + splay_delete_tree(*config_tree); *config_tree = NULL; } config_t *new_config(void) { - return xmalloc_and_zero(sizeof(config_t)); + return xzalloc(sizeof(config_t)); } void free_config(config_t *cfg) { @@ -87,18 +86,18 @@ void free_config(config_t *cfg) { free(cfg); } -void config_add(avl_tree_t *config_tree, config_t *cfg) { - avl_insert(config_tree, cfg); +void config_add(splay_tree_t *config_tree, config_t *cfg) { + splay_insert(config_tree, cfg); } -config_t *lookup_config(const avl_tree_t *config_tree, char *variable) { +config_t *lookup_config(splay_tree_t *config_tree, char *variable) { config_t cfg, *found; cfg.variable = variable; cfg.file = NULL; cfg.line = 0; - found = avl_search_closest_greater(config_tree, &cfg); + found = splay_search_closest_greater(config_tree, &cfg); if(!found) { return NULL; @@ -111,11 +110,11 @@ config_t *lookup_config(const avl_tree_t *config_tree, char *variable) { return found; } -config_t *lookup_config_next(const avl_tree_t *config_tree, const config_t *cfg) { - avl_node_t *node; +config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *cfg) { + splay_node_t *node; config_t *found; - node = avl_search_node(config_tree, cfg); + node = splay_search_node(config_tree, cfg); if(node) { if(node->next) { @@ -143,7 +142,7 @@ bool get_config_bool(const config_t *cfg, bool *result) { return true; } - logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -158,7 +157,7 @@ bool get_config_int(const config_t *cfg, int *result) { return true; } - logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Integer expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -188,7 +187,7 @@ bool get_config_address(const config_t *cfg, struct addrinfo **result) { return true; } - logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -202,25 +201,19 @@ bool get_config_subnet(const config_t *cfg, subnet_t **result) { } if(!str2net(&subnet, cfg->value)) { - logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Subnet expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; } - /* Teach newbies what subnets are... */ - - if(((subnet.type == SUBNET_IPV4) - && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(ipv4_t))) - || ((subnet.type == SUBNET_IPV6) - && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(ipv6_t)))) { - logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d", - cfg->variable, cfg->file, cfg->line); - return false; + if(subnetcheck(subnet)) { + *(*result = new_subnet()) = subnet; + return true; } - *(*result = new_subnet()) = subnet; - - return true; + logger(DEBUG_ALWAYS, LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d", + cfg->variable, cfg->file, cfg->line); + return false; } /* @@ -246,9 +239,10 @@ static char *readline(FILE *fp, char *buf, size_t buflen) { return buf; } - *newline = '\0'; /* kill newline */ + /* kill newline and carriage return if necessary */ + *newline = '\0'; - if(newline > p && newline[-1] == '\r') { /* and carriage return if necessary */ + if(newline > p && newline[-1] == '\r') { newline[-1] = '\0'; } @@ -282,10 +276,10 @@ config_t *parse_config_line(char *line, const char *fname, int lineno) { const char err[] = "No value for variable"; if(fname) - logger(LOG_ERR, "%s `%s' on line %d while reading config file %s", + logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' on line %d while reading config file %s", err, variable, lineno, fname); else - logger(LOG_ERR, "%s `%s' in command line option %d", + logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' in command line option %d", err, variable, lineno); return NULL; @@ -304,7 +298,7 @@ config_t *parse_config_line(char *line, const char *fname, int lineno) { Parse a configuration file and put the results in the configuration tree starting at *base. */ -bool read_config_file(avl_tree_t *config_tree, const char *fname) { +bool read_config_file(splay_tree_t *config_tree, const char *fname, bool verbose) { FILE *fp; char buffer[MAX_STRING_SIZE]; char *line; @@ -316,7 +310,7 @@ bool read_config_file(avl_tree_t *config_tree, const char *fname) { fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); + logger(verbose ? DEBUG_ALWAYS : DEBUG_CONNECTIONS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); return false; } @@ -364,11 +358,12 @@ bool read_config_file(avl_tree_t *config_tree, const char *fname) { return result; } -void read_config_options(avl_tree_t *config_tree, const char *prefix) { +void read_config_options(splay_tree_t *config_tree, const char *prefix) { size_t prefix_len = prefix ? strlen(prefix) : 0; for(const list_node_t *node = cmdline_conf->tail; node; node = node->prev) { const config_t *cfg = node->data; + config_t *new; if(!prefix) { if(strchr(cfg->variable, '.')) { @@ -381,7 +376,7 @@ void read_config_options(avl_tree_t *config_tree, const char *prefix) { } } - config_t *new = new_config(); + new = new_config(); if(prefix) { new->variable = xstrdup(cfg->variable + prefix_len + 1); @@ -403,14 +398,14 @@ bool read_server_config(void) { read_config_options(config_tree, NULL); - snprintf(fname, sizeof(fname), "%s/tinc.conf", confbase); + snprintf(fname, sizeof(fname), "%s" SLASH "tinc.conf", confbase); errno = 0; - x = read_config_file(config_tree, fname); + x = read_config_file(config_tree, fname, true); // We will try to read the conf files in the "conf.d" dir if(x) { char dname[PATH_MAX]; - snprintf(dname, sizeof(dname), "%s/conf.d", confbase); + snprintf(dname, sizeof(dname), "%s" SLASH "conf.d", confbase); DIR *dir = opendir(dname); // If we can find this dir @@ -423,12 +418,12 @@ bool read_server_config(void) { // And we try to read the ones that end with ".conf" if(l > 5 && !strcmp(".conf", & ep->d_name[ l - 5 ])) { - if((size_t)snprintf(fname, sizeof(fname), "%s/%s", dname, ep->d_name) >= sizeof(fname)) { - logger(LOG_ERR, "Pathname too long: %s/%s", dname, ep->d_name); + if((size_t)snprintf(fname, sizeof(fname), "%s" SLASH "%s", dname, ep->d_name) >= sizeof(fname)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Pathname too long: %s/%s", dname, ep->d_name); return false; } - x = read_config_file(config_tree, fname); + x = read_config_file(config_tree, fname, true); } } @@ -437,166 +432,32 @@ bool read_server_config(void) { } if(!x && errno) { - logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno)); } return x; } -bool read_connection_config(connection_t *c) { +bool read_host_config(splay_tree_t *config_tree, const char *name, bool verbose) { + read_config_options(config_tree, name); + char fname[PATH_MAX]; - bool x; - - read_config_options(c->config_tree, c->name); - - snprintf(fname, sizeof(fname), "%s/hosts/%s", confbase, c->name); - x = read_config_file(c->config_tree, fname); - - return x; + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name); + return read_config_file(config_tree, fname, verbose); } -static void disable_old_keys(const char *filename) { - char tmpfile[PATH_MAX] = ""; - char buf[1024]; - bool disabled = false; - FILE *r, *w; +bool append_config_file(const char *name, const char *key, const char *value) { + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name); - r = fopen(filename, "r"); + FILE *fp = fopen(fname, "a"); - if(!r) { - return; + if(!fp) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Cannot open config file %s: %s", fname, strerror(errno)); + return false; } - int len = snprintf(tmpfile, sizeof(tmpfile), "%s.tmp", filename); - - if(len < 0 || len >= PATH_MAX) { - fprintf(stderr, "Pathname too long: %s.tmp\n", filename); - w = NULL; - } else { - w = fopen(tmpfile, "w"); - } - - while(fgets(buf, sizeof(buf), r)) { - if(!strncmp(buf, "-----BEGIN RSA", 14)) { - buf[11] = 'O'; - buf[12] = 'L'; - buf[13] = 'D'; - disabled = true; - } else if(!strncmp(buf, "-----END RSA", 12)) { - buf[ 9] = 'O'; - buf[10] = 'L'; - buf[11] = 'D'; - disabled = true; - } - - if(w && fputs(buf, w) < 0) { - disabled = false; - break; - } - } - - if(w) { - fclose(w); - } - - fclose(r); - - if(!w && disabled) { - fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); - return; - } - - if(disabled) { -#ifdef HAVE_MINGW - // We cannot atomically replace files on Windows. - char bakfile[PATH_MAX] = ""; - snprintf(bakfile, sizeof(bakfile), "%s.bak", filename); - - if(rename(filename, bakfile) || rename(tmpfile, filename)) { - rename(bakfile, filename); -#else - - if(rename(tmpfile, filename)) { -#endif - fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); - } else { -#ifdef HAVE_MINGW - unlink(bakfile); -#endif - fprintf(stderr, "Warning: old key(s) found and disabled.\n"); - } - } - - unlink(tmpfile); + fprintf(fp, "\n# The following line was automatically added by tinc\n%s = %s\n", key, value); + fclose(fp); + return true; } - -FILE *ask_and_open(const char *filename, const char *what) { - FILE *r; - char directory[PATH_MAX]; - char line[PATH_MAX]; - char abspath[PATH_MAX]; - const char *fn; - - /* Check stdin and stdout */ - if(!isatty(0) || !isatty(1)) { - /* Argh, they are running us from a script or something. Write - the files to the current directory and let them burn in hell - for ever. */ - fn = filename; - } else { - /* Ask for a file and/or directory name. */ - fprintf(stdout, "Please enter a file to save %s to [%s]: ", - what, filename); - fflush(stdout); - - fn = readline(stdin, line, sizeof(line)); - - if(!fn) { - fprintf(stderr, "Error while reading stdin: %s\n", - strerror(errno)); - return NULL; - } - - if(!strlen(fn)) - /* User just pressed enter. */ - { - fn = filename; - } - } - -#ifdef HAVE_MINGW - - if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) { -#else - - if(fn[0] != '/') { -#endif - /* The directory is a relative path or a filename. */ - getcwd(directory, sizeof(directory)); - - if((size_t)snprintf(abspath, sizeof(abspath), "%s/%s", directory, fn) >= sizeof(abspath)) { - fprintf(stderr, "Pathname too long: %s/%s\n", directory, fn); - return NULL; - } - - fn = abspath; - } - - umask(0077); /* Disallow everything for group and other */ - - disable_old_keys(fn); - - /* Open it first to keep the inode busy */ - - r = fopen(fn, "a"); - - if(!r) { - fprintf(stderr, "Error opening file `%s': %s\n", - fn, strerror(errno)); - return NULL; - } - - return r; -} - - diff --git a/src/conf.h b/src/conf.h index 770ada7..8457124 100644 --- a/src/conf.h +++ b/src/conf.h @@ -4,7 +4,7 @@ /* conf.h -- header for conf.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2012 Guus Sliepen + 2000-2013 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 @@ -21,8 +21,9 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "avl_tree.h" #include "list.h" +#include "splay_tree.h" +#include "subnet.h" typedef struct config_t { char *variable; @@ -31,37 +32,33 @@ typedef struct config_t { int line; } config_t; -#include "subnet.h" -extern avl_tree_t *config_tree; +extern splay_tree_t *config_tree; extern int pinginterval; extern int pingtimeout; extern int maxtimeout; -extern int mintimeout; extern bool bypass_security; -extern char *confbase; -extern char *netname; extern list_t *cmdline_conf; -extern void init_configuration(avl_tree_t **config_tree); -extern void exit_configuration(avl_tree_t **config_tree); +extern void init_configuration(splay_tree_t **config_tree); +extern void exit_configuration(splay_tree_t **config_tree); extern config_t *new_config(void) __attribute__((__malloc__)); -extern void free_config(config_t *cfg); -extern void config_add(avl_tree_t *config_tree, config_t *cfg); -extern config_t *lookup_config(const avl_tree_t *config_tree, char *variable); -extern config_t *lookup_config_next(const avl_tree_t *config_tree, const config_t *cfg); -extern bool get_config_bool(const config_t *cfg, bool *result); -extern bool get_config_int(const config_t *cfg, int *result); -extern bool get_config_string(const config_t *cfg, char **result); -extern bool get_config_address(const config_t *cfg, struct addrinfo **result); -extern bool get_config_subnet(const config_t *cfg, struct subnet_t **result); +extern void free_config(config_t *config); +extern void config_add(splay_tree_t *config_tree, config_t *config); +extern config_t *lookup_config(splay_tree_t *config_tree, char *variable); +extern config_t *lookup_config_next(splay_tree_t *config_tree, const config_t *config); +extern bool get_config_bool(const config_t *config, bool *result); +extern bool get_config_int(const config_t *config, int *result); +extern bool get_config_string(const config_t *config, char **result); +extern bool get_config_address(const config_t *config, struct addrinfo **result); +extern bool get_config_subnet(const config_t *config, struct subnet_t **result); extern config_t *parse_config_line(char *line, const char *fname, int lineno); -extern bool read_config_file(avl_tree_t *config_tree, const char *fname); -extern void read_config_options(avl_tree_t *config_tree, const char *prefix); +extern bool read_config_file(splay_tree_t *config_tree, const char *filename, bool verbose); +extern void read_config_options(splay_tree_t *config_tree, const char *prefix); extern bool read_server_config(void); -extern bool read_connection_config(struct connection_t *c); -extern FILE *ask_and_open(const char *fname, const char *what); +extern bool read_host_config(splay_tree_t *config_tree, const char *name, bool verbose); +extern bool append_config_file(const char *name, const char *key, const char *value); #endif diff --git a/src/connection.c b/src/connection.c index d137af1..1c638a4 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,6 +1,6 @@ /* connection.c -- connection list management - Copyright (C) 2000-2016 Guus Sliepen , + Copyright (C) 2000-2013 Guus Sliepen , 2000-2005 Ivo Timmermans 2008 Max Rijevski @@ -21,100 +21,68 @@ #include "system.h" -#include "avl_tree.h" +#include "list.h" +#include "cipher.h" #include "conf.h" +#include "control_common.h" +#include "list.h" #include "logger.h" +#include "net.h" +#include "rsa.h" #include "subnet.h" #include "utils.h" #include "xalloc.h" -avl_tree_t *connection_tree; /* Meta connections */ +list_t *connection_list; connection_t *everyone; -static int connection_compare(const connection_t *a, const connection_t *b) { - return a < b ? -1 : a == b ? 0 : 1; -} - void init_connections(void) { - connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection); + connection_list = list_alloc((list_action_t) free_connection); everyone = new_connection(); everyone->name = xstrdup("everyone"); everyone->hostname = xstrdup("BROADCAST"); } void exit_connections(void) { - avl_delete_tree(connection_tree); + list_delete_list(connection_list); free_connection(everyone); } connection_t *new_connection(void) { - connection_t *c; - - c = xmalloc_and_zero(sizeof(connection_t)); - - if(!c) { - return NULL; - } - - gettimeofday(&c->start, NULL); - - return c; -} - -void free_connection_partially(connection_t *c) { - free(c->inkey); - free(c->outkey); - free(c->mychallenge); - free(c->hischallenge); - free(c->outbuf); - - c->inkey = NULL; - c->outkey = NULL; - c->mychallenge = NULL; - c->hischallenge = NULL; - c->outbuf = NULL; - - c->status.pinged = false; - c->status.active = false; - c->status.connecting = false; - c->status.timeout = false; - c->status.encryptout = false; - c->status.decryptin = false; - c->status.mst = false; - - c->options = 0; - c->buflen = 0; - c->reqlen = 0; - c->tcplen = 0; - c->allow_request = 0; - c->outbuflen = 0; - c->outbufsize = 0; - c->outbufstart = 0; - c->last_ping_time = 0; - c->last_flushed_time = 0; - c->inbudget = 0; - c->outbudget = 0; - - if(c->inctx) { - EVP_CIPHER_CTX_reset(c->inctx); - free(c->inctx); - c->inctx = NULL; - } - - if(c->outctx) { - EVP_CIPHER_CTX_reset(c->outctx); - free(c->outctx); - c->outctx = NULL; - } - - if(c->rsa_key) { - RSA_free(c->rsa_key); - c->rsa_key = NULL; - } + return xzalloc(sizeof(connection_t)); } void free_connection(connection_t *c) { - free_connection_partially(c); + if(!c) { + return; + } + +#ifndef DISABLE_LEGACY + cipher_close(c->incipher); + digest_close(c->indigest); + cipher_close(c->outcipher); + digest_close(c->outdigest); + rsa_free(c->rsa); +#endif + + sptps_stop(&c->sptps); + ecdsa_free(c->ecdsa); + + free(c->hischallenge); + free(c->mychallenge); + + buffer_clear(&c->inbuf); + buffer_clear(&c->outbuf); + + io_del(&c->io); + + if(c->socket > 0) { + if(c->status.tarpit) { + tarpit(c->socket); + } else { + closesocket(c->socket); + } + } free(c->name); free(c->hostname); @@ -127,25 +95,20 @@ void free_connection(connection_t *c) { } void connection_add(connection_t *c) { - avl_insert(connection_tree, c); + list_insert_tail(connection_list, c); } void connection_del(connection_t *c) { - avl_delete(connection_tree, c); + list_delete(connection_list, c); } -void dump_connections(void) { - avl_node_t *node; - connection_t *c; - - logger(LOG_DEBUG, "Connections:"); - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - logger(LOG_DEBUG, " %s at %s options %x socket %d status %04x outbuf %d/%d/%d", - c->name, c->hostname, c->options, c->socket, bitfield_to_int(&c->status, sizeof(c->status)), - c->outbufsize, c->outbufstart, c->outbuflen); +bool dump_connections(connection_t *cdump) { + for list_each(connection_t, c, connection_list) { + send_request(cdump, "%d %d %s %s %x %d %x", + CONTROL, REQ_DUMP_CONNECTIONS, + c->name, c->hostname, c->options, c->socket, + bitfield_to_int(&c->status, sizeof(c->status))); } - logger(LOG_DEBUG, "End of connections."); + return send_request(cdump, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); } diff --git a/src/connection.h b/src/connection.h index d619e85..206417b 100644 --- a/src/connection.h +++ b/src/connection.h @@ -3,7 +3,7 @@ /* connection.h -- header for connection.c - Copyright (C) 2000-2016 Guus Sliepen , + Copyright (C) 2000-2013 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,45 +21,50 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define EVP_CIPHER_CTX_reset(c) EVP_CIPHER_CTX_cleanup(c) -#endif - -#include "avl_tree.h" +#include "buffer.h" +#include "cipher.h" +#include "digest.h" +#include "rsa.h" +#include "list.h" +#include "sptps.h" #define OPTION_INDIRECT 0x0001 #define OPTION_TCPONLY 0x0002 #define OPTION_PMTU_DISCOVERY 0x0004 #define OPTION_CLAMP_MSS 0x0008 +#define OPTION_VERSION(x) ((x) >> 24) /* Top 8 bits are for protocol minor version */ typedef struct connection_status_t { - unsigned int pinged: 1; /* sent ping */ - unsigned int active: 1; /* 1 if active.. */ - unsigned int connecting: 1; /* 1 if we are waiting for a non-blocking connect() to finish */ - unsigned int unused_termreq: 1; /* the termination of this connection was requested */ - unsigned int remove: 1; /* Set to 1 if you want this connection removed */ - unsigned int timeout: 1; /* 1 if gotten timeout */ - unsigned int encryptout: 1; /* 1 if we can encrypt outgoing traffic */ - unsigned int decryptin: 1; /* 1 if we have to decrypt incoming traffic */ - unsigned int mst: 1; /* 1 if this connection is part of a minimum spanning tree */ - unsigned int proxy_passed: 1; /* 1 if we are connecting via a proxy and we have finished talking with it */ - unsigned int tarpit: 1; /* 1 if the connection should be added to the tarpit */ - unsigned int unused: 21; + unsigned int pinged: 1; /* sent ping */ + unsigned int unused_active: 1; + unsigned int connecting: 1; /* 1 if we are waiting for a non-blocking connect() to finish */ + unsigned int unused_termreq: 1; /* the termination of this connection was requested */ + unsigned int remove_unused: 1; /* Set to 1 if you want this connection removed */ + unsigned int timeout_unused: 1; /* 1 if gotten timeout */ + unsigned int encryptout: 1; /* 1 if we can encrypt outgoing traffic */ + unsigned int decryptin: 1; /* 1 if we have to decrypt incoming traffic */ + unsigned int mst: 1; /* 1 if this connection is part of a minimum spanning tree */ + unsigned int control: 1; /* 1 if this is a control connection */ + unsigned int pcap: 1; /* 1 if this is a control connection requesting packet capture */ + unsigned int log: 1; /* 1 if this is a control connection requesting log dump */ + unsigned int invitation: 1; /* 1 if this is an invitation */ + unsigned int invitation_used: 1; /* 1 if the invitation has been consumed */ + unsigned int tarpit: 1; /* 1 if the connection should be added to the tarpit */ + unsigned int unused: 17; } connection_status_t; +#include "ecdsa.h" #include "edge.h" #include "net.h" #include "node.h" typedef struct connection_t { char *name; /* name he claims to have */ + char *hostname; /* the hostname of its real ip */ union sockaddr_t address; /* his real (internet) ip */ - char *hostname; /* the hostname of its real ip */ - int protocol_version; /* used protocol */ + int protocol_major; /* used protocol */ + int protocol_minor; /* used protocol */ int socket; /* socket used for this connection */ uint32_t options; /* options for this connection */ @@ -71,53 +76,48 @@ typedef struct connection_t { struct node_t *node; /* node associated with the other end */ struct edge_t *edge; /* edge associated with this connection */ - RSA *rsa_key; /* his public/private key */ - const EVP_CIPHER *incipher; /* Cipher he will use to send data to us */ - const EVP_CIPHER *outcipher; /* Cipher we will use to send data to him */ - EVP_CIPHER_CTX *inctx; /* Context of encrypted meta data that will come from him to us */ - EVP_CIPHER_CTX *outctx; /* Context of encrypted meta data that will be sent from us to him */ - uint64_t inbudget; /* Encrypted bytes send budget */ - uint64_t outbudget; /* Encrypted bytes receive budget */ - char *inkey; /* His symmetric meta key + iv */ - char *outkey; /* Our symmetric meta key + iv */ - int inkeylength; /* Length of his key + iv */ - int outkeylength; /* Length of our key + iv */ - const EVP_MD *indigest; - const EVP_MD *outdigest; +#ifndef DISABLE_LEGACY + rsa_t *rsa; /* his public RSA key */ + cipher_t *incipher; /* Cipher he will use to send data to us */ + cipher_t *outcipher; /* Cipher we will use to send data to him */ + digest_t *indigest; + digest_t *outdigest; + uint64_t inbudget; + uint64_t outbudget; +#endif + + ecdsa_t *ecdsa; /* his public ECDSA key */ + sptps_t sptps; + int inmaclength; int outmaclength; int incompression; int outcompression; - char *mychallenge; /* challenge we received from him */ - char *hischallenge; /* challenge we sent to him */ - char buffer[MAXBUFSIZE]; /* metadata input buffer */ - int buflen; /* bytes read into buffer */ - int reqlen; /* length of incoming request */ - length_t tcplen; /* length of incoming TCPpacket */ + char *hischallenge; /* The challenge we sent to him */ + char *mychallenge; /* The challenge we received */ + + struct buffer_t inbuf; + struct buffer_t outbuf; + io_t io; /* input/output event on this metadata connection */ + int tcplen; /* length of incoming TCPpacket */ + int sptpslen; /* length of incoming SPTPS packet */ int allow_request; /* defined if there's only one request possible */ - char *outbuf; /* metadata output buffer */ - int outbufstart; /* index of first meaningful byte in output buffer */ - int outbuflen; /* number of meaningful bytes in output buffer */ - int outbufsize; /* number of bytes allocated to output buffer */ - time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */ - time_t last_flushed_time; /* last time buffer was empty. Only meaningful if outbuflen > 0 */ - avl_tree_t *config_tree; /* Pointer to configuration tree belonging to him */ + splay_tree_t *config_tree; /* Pointer to configuration tree belonging to him */ } connection_t; -extern avl_tree_t *connection_tree; +extern list_t *connection_list; extern connection_t *everyone; extern void init_connections(void); extern void exit_connections(void); extern connection_t *new_connection(void) __attribute__((__malloc__)); extern void free_connection(connection_t *c); -extern void free_connection_partially(connection_t *c); extern void connection_add(connection_t *c); extern void connection_del(connection_t *c); -extern void dump_connections(void); +extern bool dump_connections(struct connection_t *c); #endif diff --git a/src/control.c b/src/control.c new file mode 100644 index 0000000..71258b5 --- /dev/null +++ b/src/control.c @@ -0,0 +1,241 @@ +/* + control.c -- Control socket handling. + Copyright (C) 2013 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 "crypto.h" +#include "conf.h" +#include "control.h" +#include "control_common.h" +#include "graph.h" +#include "logger.h" +#include "meta.h" +#include "names.h" +#include "net.h" +#include "netutl.h" +#include "protocol.h" +#include "route.h" +#include "utils.h" +#include "xalloc.h" + +char controlcookie[65]; + +static bool control_return(connection_t *c, int type, int error) { + return send_request(c, "%d %d %d", CONTROL, type, error); +} + +static bool control_ok(connection_t *c, int type) { + return control_return(c, type, 0); +} + +bool control_h(connection_t *c, const char *request) { + int type; + + if(!c->status.control || c->allow_request != CONTROL) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unauthorized control request from %s (%s)", c->name, c->hostname); + return false; + } + + if(sscanf(request, "%*d %d", &type) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CONTROL", c->name, c->hostname); + return false; + } + + switch(type) { + case REQ_STOP: + event_exit(); + return control_ok(c, REQ_STOP); + + case REQ_DUMP_NODES: + return dump_nodes(c); + + case REQ_DUMP_EDGES: + return dump_edges(c); + + case REQ_DUMP_SUBNETS: + return dump_subnets(c); + + case REQ_DUMP_CONNECTIONS: + return dump_connections(c); + + case REQ_PURGE: + purge(); + return control_ok(c, REQ_PURGE); + + case REQ_SET_DEBUG: { + int new_level; + + if(sscanf(request, "%*d %*d %d", &new_level) != 1) { + return false; + } + + send_request(c, "%d %d %d", CONTROL, REQ_SET_DEBUG, debug_level); + + if(new_level >= 0) { + debug_level = new_level; + } + + return true; + } + + case REQ_RETRY: + retry(); + return control_ok(c, REQ_RETRY); + + case REQ_RELOAD: + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got '%s' command", "reload"); + int result = reload_configuration(); + return control_return(c, REQ_RELOAD, result); + + case REQ_DISCONNECT: { + char name[MAX_STRING_SIZE]; + bool found = false; + + if(sscanf(request, "%*d %*d " MAX_STRING, name) != 1) { + return control_return(c, REQ_DISCONNECT, -1); + } + + for list_each(connection_t, other, connection_list) { + if(strcmp(other->name, name)) { + continue; + } + + terminate_connection(other, other->edge); + found = true; + } + + return control_return(c, REQ_DISCONNECT, found ? 0 : -2); + } + + case REQ_DUMP_TRAFFIC: + return dump_traffic(c); + + case REQ_PCAP: + sscanf(request, "%*d %*d %d", &c->outmaclength); + c->status.pcap = true; + pcap = true; + return true; + + case REQ_LOG: + sscanf(request, "%*d %*d %d", &c->outcompression); + c->status.log = true; + logcontrol = true; + return true; + + default: + return send_request(c, "%d %d", CONTROL, REQ_INVALID); + } +} + +bool init_control(void) { + randomize(controlcookie, sizeof(controlcookie) / 2); + bin2hex(controlcookie, controlcookie, sizeof(controlcookie) / 2); + + mode_t mask = umask(0); + umask(mask | 077); + FILE *f = fopen(pidfilename, "w"); + umask(mask); + + if(!f) { + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot write control socket cookie file %s: %s", pidfilename, strerror(errno)); + return false; + } + + // Get the address and port of the first listening socket + + char *localhost = NULL; + sockaddr_t sa = {0}; + socklen_t len = sizeof(sa); + + // Make sure we have a valid address, and map 0.0.0.0 and :: to 127.0.0.1 and ::1. + + if(getsockname(listen_socket[0].tcp.fd, &sa.sa, &len)) { + xasprintf(&localhost, "127.0.0.1 port %s", myport); + } else { + if(sa.sa.sa_family == AF_INET) { + if(sa.in.sin_addr.s_addr == 0) { + sa.in.sin_addr.s_addr = htonl(0x7f000001); + } + } else if(sa.sa.sa_family == AF_INET6) { + static const uint8_t zero[16] = {0}; + + if(!memcmp(sa.in6.sin6_addr.s6_addr, zero, sizeof(zero))) { + sa.in6.sin6_addr.s6_addr[15] = 1; + } + } + + localhost = sockaddr2hostname(&sa); + } + + fprintf(f, "%d %s %s\n", (int)getpid(), controlcookie, localhost); + + free(localhost); + fclose(f); + +#ifndef HAVE_MINGW + int unix_fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if(unix_fd < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create UNIX socket: %s", sockstrerror(sockerrno)); + return false; + } + + struct sockaddr_un sa_un; + + sa_un.sun_family = AF_UNIX; + + strncpy(sa_un.sun_path, unixsocketname, sizeof(sa_un.sun_path)); + + sa_un.sun_path[sizeof(sa_un.sun_path) - 1] = 0; + + if(connect(unix_fd, (struct sockaddr *)&sa_un, sizeof(sa_un)) >= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "UNIX socket %s is still in use!", unixsocketname); + return false; + } + + unlink(unixsocketname); + + umask(mask | 077); + int result = bind(unix_fd, (struct sockaddr *)&sa_un, sizeof(sa_un)); + umask(mask); + + if(result < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind UNIX socket to %s: %s", unixsocketname, sockstrerror(sockerrno)); + return false; + } + + if(listen(unix_fd, 3) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not listen on UNIX socket %s: %s", unixsocketname, sockstrerror(sockerrno)); + return false; + } + + io_add(&unix_socket, handle_new_unix_connection, &unix_socket, unix_fd, IO_READ); +#endif + + return true; +} + +void exit_control(void) { +#ifndef HAVE_MINGW + unlink(unixsocketname); + io_del(&unix_socket); + close(unix_socket.fd); +#endif + + unlink(pidfilename); +} diff --git a/src/control.h b/src/control.h new file mode 100644 index 0000000..9398410 --- /dev/null +++ b/src/control.h @@ -0,0 +1,27 @@ +#ifndef TINC_CONTROL_H +#define TINC_CONTROL_H + +/* + control.h -- header for control.c. + Copyright (C) 2007 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. +*/ + +extern bool init_control(void); +extern void exit_control(void); +extern char controlcookie[]; + +#endif diff --git a/src/control_common.h b/src/control_common.h new file mode 100644 index 0000000..2be4abd --- /dev/null +++ b/src/control_common.h @@ -0,0 +1,48 @@ +#ifndef TINC_CONTROL_COMMON_H +#define TINC_CONTROL_COMMON_H + +/* + control_protocol.h -- control socket protocol. + Copyright (C) 2007 Scott Lamb + 2009-2012 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 "protocol.h" + +enum request_type { + REQ_INVALID = -1, + REQ_STOP = 0, + REQ_RELOAD, + REQ_RESTART, + REQ_DUMP_NODES, + REQ_DUMP_EDGES, + REQ_DUMP_SUBNETS, + REQ_DUMP_CONNECTIONS, + REQ_DUMP_GRAPH, + REQ_PURGE, + REQ_SET_DEBUG, + REQ_RETRY, + REQ_CONNECT, + REQ_DISCONNECT, + REQ_DUMP_TRAFFIC, + REQ_PCAP, + REQ_LOG, +}; + +#define TINC_CTL_VERSION_CURRENT 0 + +#endif diff --git a/src/crypto.h b/src/crypto.h new file mode 100644 index 0000000..74bab1b --- /dev/null +++ b/src/crypto.h @@ -0,0 +1,27 @@ +#ifndef TINC_CRYPTO_H +#define TINC_CRYPTO_H + +/* + crypto.h -- header for crypto.c + Copyright (C) 2007-2013 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. +*/ + +extern void crypto_init(void); +extern void crypto_exit(void); +extern void randomize(void *buf, size_t buflen); + +#endif diff --git a/src/cygwin/device.c b/src/cygwin/device.c deleted file mode 100644 index 1165d67..0000000 --- a/src/cygwin/device.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - device.c -- Interaction with Windows tap driver in a Cygwin environment - Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2016 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 "../net.h" - -#include -#include - -#include "../conf.h" -#include "../device.h" -#include "../logger.h" -#include "../route.h" -#include "../utils.h" -#include "../xalloc.h" - -#include "../mingw/common.h" - -int device_fd = -1; -static HANDLE device_handle = INVALID_HANDLE_VALUE; -char *device = NULL; -char *iface = NULL; -static const char *device_info = "Windows tap device"; - -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - -static pid_t reader_pid; -static int sp[2]; - -static bool setup_device(void) { - HKEY key, key2; - int i, err; - - char regpath[1024]; - char adapterid[1024]; - char adaptername[1024]; - char tapname[1024]; - char gelukt = 0; - long len; - - bool found = false; - - get_config_string(lookup_config(config_tree, "Device"), &device); - get_config_string(lookup_config(config_tree, "Interface"), &iface); - - if(device && iface) { - logger(LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected"); - } - - /* Open registry and look for network adapters */ - - if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) { - logger(LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); - return false; - } - - for(i = 0; ; i++) { - len = sizeof(adapterid); - - if(RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) { - break; - } - - /* Find out more about this adapter */ - - snprintf(regpath, sizeof(regpath), "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid); - - if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) { - continue; - } - - len = sizeof(adaptername); - err = RegQueryValueEx(key2, "Name", 0, 0, adaptername, &len); - - RegCloseKey(key2); - - if(err) { - continue; - } - - if(device) { - if(!strcmp(device, adapterid)) { - found = true; - break; - } else { - continue; - } - } - - if(iface) { - if(!strcmp(iface, adaptername)) { - found = true; - break; - } else { - continue; - } - } - - snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, adapterid); - device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); - - if(device_handle != INVALID_HANDLE_VALUE) { - CloseHandle(device_handle); - found = true; - break; - } - } - - RegCloseKey(key); - - if(!found) { - logger(LOG_ERR, "No Windows tap device found!"); - return false; - } - - if(!device) { - device = xstrdup(adapterid); - } - - if(!iface) { - iface = xstrdup(adaptername); - } - - snprintf(tapname, sizeof(tapname), USERMODEDEVICEDIR "%s" TAPSUFFIX, device); - - /* Now we are going to open this device twice: once for reading and once for writing. - We do this because apparently it isn't possible to check for activity in the select() loop. - Furthermore I don't really know how to do it the "Windows" way. */ - - if(socketpair(AF_UNIX, SOCK_DGRAM, PF_UNIX, sp)) { - logger(LOG_DEBUG, "System call `%s' failed: %s", "socketpair", strerror(errno)); - return false; - } - - /* The parent opens the tap device for writing. */ - - device_handle = CreateFile(tapname, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); - - if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "Could not open Windows tap device %s (%s) for writing: %s", device, iface, winerror(GetLastError())); - return false; - } - - device_fd = sp[0]; - - /* Get MAC address from tap device */ - - if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) { - logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); - return false; - } - - if(routing_mode == RMODE_ROUTER) { - overwrite_mac = 1; - } - - /* Now we start the child */ - - reader_pid = fork(); - - if(reader_pid == -1) { - logger(LOG_DEBUG, "System call `%s' failed: %s", "fork", strerror(errno)); - return false; - } - - if(!reader_pid) { - /* The child opens the tap device for reading, blocking. - It passes everything it reads to the socket. */ - - char buf[MTU]; - long lenin; - - CloseHandle(device_handle); - - device_handle = CreateFile(tapname, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); - - if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "Could not open Windows tap device %s (%s) for reading: %s", device, iface, winerror(GetLastError())); - buf[0] = 0; - write(sp[1], buf, 1); - exit(1); - } - - logger(LOG_DEBUG, "Tap reader forked and running."); - - /* Notify success */ - - buf[0] = 1; - write(sp[1], buf, 1); - - /* Pass packets */ - - for(;;) { - ReadFile(device_handle, buf, MTU, &lenin, NULL); - write(sp[1], buf, lenin); - } - } - - read(device_fd, &gelukt, 1); - - if(gelukt != 1) { - logger(LOG_DEBUG, "Tap reader failed!"); - return false; - } - - logger(LOG_INFO, "%s (%s) is a %s", device, iface, device_info); - - return true; -} - -static void close_device(void) { - close(sp[0]); - close(sp[1]); - CloseHandle(device_handle); - - kill(reader_pid, SIGKILL); - - free(device); - free(iface); -} - -static bool read_packet(vpn_packet_t *packet) { - int lenin; - - if((lenin = read(sp[0], packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, - device, strerror(errno)); - return false; - } - - packet->len = lenin; - - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, - device_info); - - return true; -} - -static bool write_packet(vpn_packet_t *packet) { - long lenout; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); - - if(!WriteFile(device_handle, packet->data, packet->len, &lenout, NULL)) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); - return false; - } - - device_total_out += packet->len; - - return true; -} - -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - -const devops_t os_devops = { - .setup = setup_device, - .close = close_device, - .read = read_packet, - .write = write_packet, - .dump_stats = dump_device_stats, -}; diff --git a/src/device.h b/src/device.h index 6bfc44d..c85671b 100644 --- a/src/device.h +++ b/src/device.h @@ -25,21 +25,22 @@ extern int device_fd; extern char *device; - extern char *iface; typedef struct devops_t { bool (*setup)(void); void (*close)(void); - bool (*read)(struct vpn_packet_t *packet); - bool (*write)(struct vpn_packet_t *packet); - void (*dump_stats)(void); + bool (*read)(struct vpn_packet_t *); + bool (*write)(struct vpn_packet_t *); + void (*enable)(void); /* optional */ + void (*disable)(void); /* optional */ } devops_t; extern const devops_t os_devops; extern const devops_t dummy_devops; extern const devops_t raw_socket_devops; extern const devops_t multicast_devops; +extern const devops_t fd_devops; extern const devops_t uml_devops; extern const devops_t vde_devops; extern devops_t devops; diff --git a/src/digest.h b/src/digest.h new file mode 100644 index 0000000..6d7cb41 --- /dev/null +++ b/src/digest.h @@ -0,0 +1,42 @@ +#ifndef TINC_DIGEST_H +#define TINC_DIGEST_H + +/* + digest.h -- header file digest.c + Copyright (C) 2007-2016 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. +*/ + +#define DIGEST_MAX_SIZE 64 + +#ifndef DISABLE_LEGACY + +typedef struct digest digest_t; + +extern digest_t *digest_open_by_name(const char *name, int maclength) __attribute__((__malloc__)); +extern digest_t *digest_open_by_nid(int nid, int maclength) __attribute__((__malloc__)); +extern void digest_close(digest_t *digest); +extern bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) __attribute__((__warn_unused_result__)); +extern bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *digestdata) __attribute__((__warn_unused_result__)); +extern bool digest_set_key(digest_t *digest, const void *key, size_t len) __attribute__((__warn_unused_result__)); +extern int digest_get_nid(const digest_t *digest); +extern size_t digest_keylength(const digest_t *digest); +extern size_t digest_length(const digest_t *digest); +extern bool digest_active(const digest_t *digest); + +#endif + +#endif diff --git a/src/dropin.c b/src/dropin.c index 93511f1..0cde7a0 100644 --- a/src/dropin.c +++ b/src/dropin.c @@ -1,7 +1,7 @@ /* dropin.c -- a set of drop-in replacements for libc functions Copyright (C) 2000-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2018 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 @@ -82,6 +82,8 @@ int daemon(int nochdir, int noclose) { return 0; #else + (void)nochdir; + (void)noclose; return -1; #endif } @@ -107,7 +109,6 @@ int vasprintf(char **buf, const char *fmt, va_list ap) { va_copy(aq, ap); status = vsnprintf(*buf, len, fmt, aq); - buf[len - 1] = 0; va_end(aq); if(status >= 0) { @@ -115,7 +116,7 @@ int vasprintf(char **buf, const char *fmt, va_list ap) { } if(status > len - 1) { - len = status; + len = status + 1; va_copy(aq, ap); status = vsnprintf(*buf, len, fmt, aq); va_end(aq); @@ -127,16 +128,26 @@ int vasprintf(char **buf, const char *fmt, va_list ap) { #ifndef HAVE_GETTIMEOFDAY int gettimeofday(struct timeval *tv, void *tz) { +#ifdef HAVE_MINGW + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uint64_t lt = (uint64_t)ft.dwLowDateTime | ((uint64_t)ft.dwHighDateTime << 32); + lt -= 116444736000000000ULL; + tv->tv_sec = lt / 10000000; + tv->tv_usec = (lt / 10) % 1000000; +#else +#warning No high resolution time source! tv->tv_sec = time(NULL); tv->tv_usec = 0; +#endif return 0; } #endif -#ifndef HAVE_USLEEP -int usleep(long long usec) { - struct timeval tv = {usec / 1000000, (usec / 1000) % 1000}; - select(0, NULL, NULL, NULL, &tv); - return 0; +#ifndef HAVE_NANOSLEEP +int nanosleep(const struct timespec *req, struct timespec *rem) { + (void)rem; + struct timeval tv = {req->tv_sec, req->tv_nsec / 1000}; + return select(0, NULL, NULL, NULL, &tv); } #endif diff --git a/src/dropin.h b/src/dropin.h index 012099b..5033f90 100644 --- a/src/dropin.h +++ b/src/dropin.h @@ -4,7 +4,7 @@ /* dropin.h -- header file for dropin.c Copyright (C) 2000-2005 Ivo Timmermans, - 2000-2011 Guus Sliepen + 2000-2016 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 @@ -21,28 +21,50 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "fake-getaddrinfo.h" -#include "fake-getnameinfo.h" - #ifndef HAVE_DAEMON -extern int daemon(int nochdir, int noclose); -#endif - -#ifndef HAVE_GET_CURRENT_DIR_NAME -extern char *get_current_dir_name(void); +extern int daemon(int, int); #endif #ifndef HAVE_ASPRINTF -extern int asprintf(char **buf, const char *fmt, ...); -extern int vasprintf(char **buf, const char *fmt, va_list ap); +extern int asprintf(char **, const char *, ...); +extern int vasprintf(char **, const char *, va_list ap); #endif #ifndef HAVE_GETTIMEOFDAY -extern int gettimeofday(struct timeval *tv, void *tz); +extern int gettimeofday(struct timeval *, void *); #endif -#ifndef HAVE_USLEEP -extern int usleep(long long usec); +#ifndef HAVE_NANOSLEEP +extern int nanosleep(const struct timespec *req, struct timespec *rem); +#endif + +#ifndef timeradd +#define timeradd(a, b, r) do {\ + (r)->tv_sec = (a)->tv_sec + (b)->tv_sec;\ + (r)->tv_usec = (a)->tv_usec + (b)->tv_usec;\ + if((r)->tv_usec >= 1000000)\ + (r)->tv_sec++, (r)->tv_usec -= 1000000;\ + } while (0) +#endif + +#ifndef timersub +#define timersub(a, b, r) do {\ + (r)->tv_sec = (a)->tv_sec - (b)->tv_sec;\ + (r)->tv_usec = (a)->tv_usec - (b)->tv_usec;\ + if((r)->tv_usec < 0)\ + (r)->tv_sec--, (r)->tv_usec += 1000000;\ + } while (0) +#endif + +#ifdef HAVE_MINGW +#define mkdir(a, b) mkdir(a) +#ifndef SHUT_RDWR +#define SHUT_RDWR SD_BOTH +#endif +#endif + +#ifndef EAI_SYSTEM +#define EAI_SYSTEM 0 #endif #endif diff --git a/src/dummy_device.c b/src/dummy_device.c index d1d751b..15f0654 100644 --- a/src/dummy_device.c +++ b/src/dummy_device.c @@ -1,6 +1,6 @@ /* device.c -- Dummy device - Copyright (C) 2011 Guus Sliepen + Copyright (C) 2011-2012 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 @@ -26,19 +26,14 @@ static const char *device_info = "dummy device"; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - static bool setup_device(void) { device = xstrdup("dummy"); iface = xstrdup("dummy"); - logger(LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); return true; } static void close_device(void) { - free(device); - free(iface); } static bool read_packet(vpn_packet_t *packet) { @@ -47,20 +42,13 @@ static bool read_packet(vpn_packet_t *packet) { } static bool write_packet(vpn_packet_t *packet) { - device_total_out += packet->len; + (void)packet; return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t dummy_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/ecdh.h b/src/ecdh.h new file mode 100644 index 0000000..0bfc271 --- /dev/null +++ b/src/ecdh.h @@ -0,0 +1,34 @@ +#ifndef TINC_ECDH_H +#define TINC_ECDH_H + +/* + ecdh.h -- header file for ecdh.c + Copyright (C) 2011-2013 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. +*/ + +#define ECDH_SIZE 32 +#define ECDH_SHARED_SIZE 32 + +#ifndef TINC_ECDH_INTERNAL +typedef struct ecdh ecdh_t; +#endif + +extern ecdh_t *ecdh_generate_public(void *pubkey) __attribute__((__malloc__)); +extern bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) __attribute__((__warn_unused_result__)); +extern void ecdh_free(ecdh_t *ecdh); + +#endif diff --git a/src/ecdsa.h b/src/ecdsa.h new file mode 100644 index 0000000..6cb5434 --- /dev/null +++ b/src/ecdsa.h @@ -0,0 +1,37 @@ +#ifndef TINC_ECDSA_H +#define TINC_ECDSA_H + +/* + ecdsa.h -- ECDSA key handling + Copyright (C) 2011-2013 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. +*/ + +#ifndef TINC_ECDSA_INTERNAL +typedef struct ecdsa ecdsa_t; +#endif + +extern ecdsa_t *ecdsa_set_base64_public_key(const char *p) __attribute__((__malloc__)); +extern char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa); +extern ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) __attribute__((__malloc__)); +extern ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) __attribute__((__malloc__)); +extern size_t ecdsa_size(ecdsa_t *ecdsa); +extern bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t inlen, void *out) __attribute__((__warn_unused_result__)); +extern bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t inlen, const void *out) __attribute__((__warn_unused_result__)); +extern bool ecdsa_active(ecdsa_t *ecdsa); +extern void ecdsa_free(ecdsa_t *ecdsa); + +#endif diff --git a/src/ecdsagen.h b/src/ecdsagen.h new file mode 100644 index 0000000..48cd25b --- /dev/null +++ b/src/ecdsagen.h @@ -0,0 +1,29 @@ +#ifndef TINC_ECDSAGEN_H +#define TINC_ECDSAGEN_H + +/* + ecdsagen.h -- ECDSA key generation and export + Copyright (C) 2011-2013 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 "ecdsa.h" + +extern ecdsa_t *ecdsa_generate(void) __attribute__((__malloc__)); +extern bool ecdsa_write_pem_public_key(ecdsa_t *ecdsa, FILE *fp) __attribute__((__warn_unused_result__)); +extern bool ecdsa_write_pem_private_key(ecdsa_t *ecdsa, FILE *fp) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/ed25519/ecdh.c b/src/ed25519/ecdh.c new file mode 100644 index 0000000..302fafd --- /dev/null +++ b/src/ed25519/ecdh.c @@ -0,0 +1,51 @@ +/* + ecdh.c -- Diffie-Hellman key exchange handling + Copyright (C) 2011-2013 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 "ed25519.h" + +#define TINC_ECDH_INTERNAL +typedef struct ecdh_t { + uint8_t private[64]; +} ecdh_t; + +#include "../crypto.h" +#include "../ecdh.h" +#include "../xalloc.h" + +ecdh_t *ecdh_generate_public(void *pubkey) { + ecdh_t *ecdh = xzalloc(sizeof(*ecdh)); + + uint8_t seed[32]; + randomize(seed, sizeof(seed)); + ed25519_create_keypair(pubkey, ecdh->private, seed); + + return ecdh; +} + +bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { + ed25519_key_exchange(shared, pubkey, ecdh->private); + free(ecdh); + return true; +} + +void ecdh_free(ecdh_t *ecdh) { + free(ecdh); +} diff --git a/src/ed25519/ecdsa.c b/src/ed25519/ecdsa.c new file mode 100644 index 0000000..4bd7155 --- /dev/null +++ b/src/ed25519/ecdsa.c @@ -0,0 +1,168 @@ +/* + ecdsa.c -- ECDSA key handling + Copyright (C) 2011-2013 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 "ed25519.h" + +#define TINC_ECDSA_INTERNAL +typedef struct { + uint8_t private[64]; + uint8_t public[32]; +} ecdsa_t; + +#include "../logger.h" +#include "../ecdsa.h" +#include "../utils.h" +#include "../xalloc.h" + +// Get and set ECDSA keys +// +ecdsa_t *ecdsa_set_base64_public_key(const char *p) { + int len = strlen(p); + + if(len != 43) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid size %d for public key!", len); + return 0; + } + + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + len = b64decode(p, ecdsa->public, len); + + if(len != 32) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid format of public key! len = %d", len); + free(ecdsa); + return 0; + } + + return ecdsa; +} + +char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) { + char *base64 = xmalloc(44); + b64encode(ecdsa->public, base64, sizeof(ecdsa->public)); + + return base64; +} + +// Read PEM ECDSA keys + +static bool read_pem(FILE *fp, const char *type, void *vbuf, size_t size) { + char line[1024]; + bool data = false; + size_t typelen = strlen(type); + char *buf = vbuf; + + while(fgets(line, sizeof(line), fp)) { + if(!data) { + if(strncmp(line, "-----BEGIN ", 11)) { + continue; + } + + if(strncmp(line + 11, type, typelen)) { + continue; + } + + data = true; + continue; + } + + if(!strncmp(line, "-----END ", 9)) { + break; + } + + size_t linelen = strcspn(line, "\r\n"); + size_t len = b64decode(line, line, linelen); + + if(!len) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid base64 data in PEM file\n"); + errno = EINVAL; + return false; + } + + if(len > size) { + logger(DEBUG_ALWAYS, LOG_ERR, "Too much base64 data in PEM file\n"); + errno = EINVAL; + return false; + } + + memcpy(buf, line, len); + buf += len; + size -= len; + } + + if(size) { + if(data) { + errno = EINVAL; + logger(DEBUG_ALWAYS, LOG_ERR, "Too little base64 data in PEM file\n"); + } else { + errno = ENOENT; + } + + return false; + } + + return true; +} + +ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) { + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + + if(read_pem(fp, "ED25519 PUBLIC KEY", ecdsa->public, sizeof(ecdsa->public))) { + return ecdsa; + } + + free(ecdsa); + return 0; +} + +ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) { + ecdsa_t *ecdsa = xmalloc(sizeof(*ecdsa)); + + if(read_pem(fp, "ED25519 PRIVATE KEY", ecdsa->private, sizeof(*ecdsa))) { + return ecdsa; + } + + free(ecdsa); + return 0; +} + +size_t ecdsa_size(ecdsa_t *ecdsa) { + (void)ecdsa; + return 64; +} + +// TODO: standardise output format? + +bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t len, void *sig) { + ed25519_sign(sig, in, len, ecdsa->public, ecdsa->private); + return true; +} + +bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t len, const void *sig) { + return ed25519_verify(sig, in, len, ecdsa->public); +} + +bool ecdsa_active(ecdsa_t *ecdsa) { + return ecdsa; +} + +void ecdsa_free(ecdsa_t *ecdsa) { + free(ecdsa); +} diff --git a/src/ed25519/ecdsagen.c b/src/ed25519/ecdsagen.c new file mode 100644 index 0000000..ede5136 --- /dev/null +++ b/src/ed25519/ecdsagen.c @@ -0,0 +1,73 @@ +/* + ecdsagen.c -- ECDSA key generation and export + Copyright (C) 2011-2013 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 "ed25519.h" + +#define TINC_ECDSA_INTERNAL +typedef struct { + uint8_t private[64]; + uint8_t public[32]; +} ecdsa_t; + +#include "../crypto.h" +#include "../ecdsagen.h" +#include "../utils.h" +#include "../xalloc.h" + +// Generate ECDSA key + +ecdsa_t *ecdsa_generate(void) { + ecdsa_t *ecdsa = xzalloc(sizeof(*ecdsa)); + + uint8_t seed[32]; + randomize(seed, sizeof(seed)); + ed25519_create_keypair(ecdsa->public, ecdsa->private, seed); + + return ecdsa; +} + +// Write PEM ECDSA keys + +static bool write_pem(FILE *fp, const char *type, void *vbuf, size_t size) { + fprintf(fp, "-----BEGIN %s-----\n", type); + + char *buf = vbuf; + char base64[65]; + + while(size) { + size_t todo = size > 48 ? 48 : size; + b64encode(buf, base64, todo); + fprintf(fp, "%s\n", base64); + buf += todo; + size -= todo; + } + + fprintf(fp, "-----END %s-----\n", type); + return !ferror(fp); +} + +bool ecdsa_write_pem_public_key(ecdsa_t *ecdsa, FILE *fp) { + return write_pem(fp, "ED25519 PUBLIC KEY", ecdsa->public, sizeof(ecdsa->public)); +} + +bool ecdsa_write_pem_private_key(ecdsa_t *ecdsa, FILE *fp) { + return write_pem(fp, "ED25519 PRIVATE KEY", ecdsa->private, sizeof(*ecdsa)); +} diff --git a/src/ed25519/ed25519.h b/src/ed25519/ed25519.h new file mode 100644 index 0000000..65b191e --- /dev/null +++ b/src/ed25519/ed25519.h @@ -0,0 +1,37 @@ +#ifndef ED25519_H +#define ED25519_H + +#include + +#if defined(_WIN32) +#if defined(ED25519_BUILD_DLL) +#define ED25519_DECLSPEC __declspec(dllexport) +#elif defined(ED25519_DLL) +#define ED25519_DECLSPEC __declspec(dllimport) +#else +#define ED25519_DECLSPEC +#endif +#else +#define ED25519_DECLSPEC +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ED25519_NO_SEED +int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed); +#endif + +void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed); +void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key); +int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *private_key); +void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ed25519/fe.c b/src/ed25519/fe.c new file mode 100644 index 0000000..df907a7 --- /dev/null +++ b/src/ed25519/fe.c @@ -0,0 +1,1511 @@ +#include "fixedint.h" +#include "fe.h" + + +/* + helper functions +*/ +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = in[0]; + result |= shlu64(in[1], 8); + result |= shlu64(in[2], 16); + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = in[0]; + result |= shlu64(in[1], 8); + result |= shlu64(in[2], 16); + result |= shlu64(in[3], 24); + + return result; +} + + + +/* + h = 0 +*/ + +void fe_0(fe h) { + h[0] = 0; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = 1 +*/ + +void fe_1(fe h) { + h[0] = 1; + h[1] = 0; + h[2] = 0; + h[3] = 0; + h[4] = 0; + h[5] = 0; + h[6] = 0; + h[7] = 0; + h[8] = 0; + h[9] = 0; +} + + + +/* + h = f + g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + + Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_add(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 + g0; + int32_t h1 = f1 + g1; + int32_t h2 = f2 + g2; + int32_t h3 = f3 + g3; + int32_t h4 = f4 + g4; + int32_t h5 = f5 + g5; + int32_t h6 = f6 + g6; + int32_t h7 = f7 + g7; + int32_t h8 = f8 + g8; + int32_t h9 = f9 + g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* + Replace (f,g) with (g,g) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cmov(fe f, const fe g, unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + + b = (unsigned int)(- (int) b); /* silence warning */ + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; +} + +/* + Replace (f,g) with (g,f) if b == 1; + replace (f,g) with (f,g) if b == 0. + + Preconditions: b in {0,1}. +*/ + +void fe_cswap(fe f, fe g, unsigned int b) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t x0 = f0 ^ g0; + int32_t x1 = f1 ^ g1; + int32_t x2 = f2 ^ g2; + int32_t x3 = f3 ^ g3; + int32_t x4 = f4 ^ g4; + int32_t x5 = f5 ^ g5; + int32_t x6 = f6 ^ g6; + int32_t x7 = f7 ^ g7; + int32_t x8 = f8 ^ g8; + int32_t x9 = f9 ^ g9; + b = -b; + x0 &= b; + x1 &= b; + x2 &= b; + x3 &= b; + x4 &= b; + x5 &= b; + x6 &= b; + x7 &= b; + x8 &= b; + x9 &= b; + f[0] = f0 ^ x0; + f[1] = f1 ^ x1; + f[2] = f2 ^ x2; + f[3] = f3 ^ x3; + f[4] = f4 ^ x4; + f[5] = f5 ^ x5; + f[6] = f6 ^ x6; + f[7] = f7 ^ x7; + f[8] = f8 ^ x8; + f[9] = f9 ^ x9; + g[0] = g0 ^ x0; + g[1] = g1 ^ x1; + g[2] = g2 ^ x2; + g[3] = g3 ^ x3; + g[4] = g4 ^ x4; + g[5] = g5 ^ x5; + g[6] = g6 ^ x6; + g[7] = g7 ^ x7; + g[8] = g8 ^ x8; + g[9] = g9 ^ x9; +} + + + +/* + h = f +*/ + +void fe_copy(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + + h[0] = f0; + h[1] = f1; + h[2] = f2; + h[3] = f3; + h[4] = f4; + h[5] = f5; + h[6] = f6; + h[7] = f7; + h[8] = f8; + h[9] = f9; +} + + + +/* + Ignores top bit of h. +*/ + +void fe_frombytes(fe h, const unsigned char *s) { + int64_t h0 = load_4(s); + int64_t h1 = load_3(s + 4) << 6; + int64_t h2 = load_3(s + 7) << 5; + int64_t h3 = load_3(s + 10) << 3; + int64_t h4 = load_3(s + 13) << 2; + int64_t h5 = load_4(s + 16); + int64_t h6 = load_3(s + 20) << 7; + int64_t h7 = load_3(s + 23) << 5; + int64_t h8 = load_3(s + 26) << 4; + int64_t h9 = (load_3(s + 29) & 8388607) << 2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shl64(carry9, 25); + carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= shl64(carry1, 25); + carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= shl64(carry3, 25); + carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= shl64(carry5, 25); + carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= shl64(carry7, 25); + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= shl64(carry2, 26); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= shl64(carry6, 26); + carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= shl64(carry8, 26); + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +void fe_invert(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + fe t3; + int i; + + fe_sq(t0, z); + + for(i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for(i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t2, t0); + + for(i = 1; i < 1; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t1, t2); + fe_sq(t2, t1); + + for(i = 1; i < 5; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for(i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for(i = 1; i < 20; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for(i = 1; i < 10; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t2, t1); + + for(i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t2, t2, t1); + fe_sq(t3, t2); + + for(i = 1; i < 100; ++i) { + fe_sq(t3, t3); + } + + fe_mul(t2, t3, t2); + fe_sq(t2, t2); + + for(i = 1; i < 50; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for(i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(out, t1, t0); +} + + + +/* + return 1 if f is in {1,3,5,...,q-2} + return 0 if f is in {0,2,4,...,q-1} + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnegative(const fe f) { + unsigned char s[32]; + + fe_tobytes(s, f); + + return s[0] & 1; +} + + + +/* + return 1 if f == 0 + return 0 if f != 0 + + Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +int fe_isnonzero(const fe f) { + unsigned char s[32]; + unsigned char r; + + fe_tobytes(s, f); + + r = s[0]; +#define F(i) r |= s[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); +#undef F + + return r != 0; +} + + + +/* + h = f * g + Can overlap h with f or g. + + Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + + Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. + */ + +/* +Notes on implementation strategy: + +Using schoolbook multiplication. +Karatsuba would save a little in some cost models. + +Most multiplications by 2 and 19 are 32-bit precomputations; +cheaper than 64-bit postcomputations. + +There is one remaining multiplication by 19 in the carry chain; +one *19 precomputation can be merged into this, +but the resulting data flow is considerably less clean. + +There are 12 carries below. +10 of them are 2-way parallelizable and vectorizable. +Can get away with 11 carries, but then data flow is much deeper. + +With tighter constraints on inputs can squeeze carries into int32. +*/ + +void fe_mul(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */ + int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */ + int32_t g3_19 = 19 * g3; + int32_t g4_19 = 19 * g4; + int32_t g5_19 = 19 * g5; + int32_t g6_19 = 19 * g6; + int32_t g7_19 = 19 * g7; + int32_t g8_19 = 19 * g8; + int32_t g9_19 = 19 * g9; + int32_t f1_2 = 2 * f1; + int32_t f3_2 = 2 * f3; + int32_t f5_2 = 2 * f5; + int32_t f7_2 = 2 * f7; + int32_t f9_2 = 2 * f9; + int64_t f0g0 = f0 * (int64_t) g0; + int64_t f0g1 = f0 * (int64_t) g1; + int64_t f0g2 = f0 * (int64_t) g2; + int64_t f0g3 = f0 * (int64_t) g3; + int64_t f0g4 = f0 * (int64_t) g4; + int64_t f0g5 = f0 * (int64_t) g5; + int64_t f0g6 = f0 * (int64_t) g6; + int64_t f0g7 = f0 * (int64_t) g7; + int64_t f0g8 = f0 * (int64_t) g8; + int64_t f0g9 = f0 * (int64_t) g9; + int64_t f1g0 = f1 * (int64_t) g0; + int64_t f1g1_2 = f1_2 * (int64_t) g1; + int64_t f1g2 = f1 * (int64_t) g2; + int64_t f1g3_2 = f1_2 * (int64_t) g3; + int64_t f1g4 = f1 * (int64_t) g4; + int64_t f1g5_2 = f1_2 * (int64_t) g5; + int64_t f1g6 = f1 * (int64_t) g6; + int64_t f1g7_2 = f1_2 * (int64_t) g7; + int64_t f1g8 = f1 * (int64_t) g8; + int64_t f1g9_38 = f1_2 * (int64_t) g9_19; + int64_t f2g0 = f2 * (int64_t) g0; + int64_t f2g1 = f2 * (int64_t) g1; + int64_t f2g2 = f2 * (int64_t) g2; + int64_t f2g3 = f2 * (int64_t) g3; + int64_t f2g4 = f2 * (int64_t) g4; + int64_t f2g5 = f2 * (int64_t) g5; + int64_t f2g6 = f2 * (int64_t) g6; + int64_t f2g7 = f2 * (int64_t) g7; + int64_t f2g8_19 = f2 * (int64_t) g8_19; + int64_t f2g9_19 = f2 * (int64_t) g9_19; + int64_t f3g0 = f3 * (int64_t) g0; + int64_t f3g1_2 = f3_2 * (int64_t) g1; + int64_t f3g2 = f3 * (int64_t) g2; + int64_t f3g3_2 = f3_2 * (int64_t) g3; + int64_t f3g4 = f3 * (int64_t) g4; + int64_t f3g5_2 = f3_2 * (int64_t) g5; + int64_t f3g6 = f3 * (int64_t) g6; + int64_t f3g7_38 = f3_2 * (int64_t) g7_19; + int64_t f3g8_19 = f3 * (int64_t) g8_19; + int64_t f3g9_38 = f3_2 * (int64_t) g9_19; + int64_t f4g0 = f4 * (int64_t) g0; + int64_t f4g1 = f4 * (int64_t) g1; + int64_t f4g2 = f4 * (int64_t) g2; + int64_t f4g3 = f4 * (int64_t) g3; + int64_t f4g4 = f4 * (int64_t) g4; + int64_t f4g5 = f4 * (int64_t) g5; + int64_t f4g6_19 = f4 * (int64_t) g6_19; + int64_t f4g7_19 = f4 * (int64_t) g7_19; + int64_t f4g8_19 = f4 * (int64_t) g8_19; + int64_t f4g9_19 = f4 * (int64_t) g9_19; + int64_t f5g0 = f5 * (int64_t) g0; + int64_t f5g1_2 = f5_2 * (int64_t) g1; + int64_t f5g2 = f5 * (int64_t) g2; + int64_t f5g3_2 = f5_2 * (int64_t) g3; + int64_t f5g4 = f5 * (int64_t) g4; + int64_t f5g5_38 = f5_2 * (int64_t) g5_19; + int64_t f5g6_19 = f5 * (int64_t) g6_19; + int64_t f5g7_38 = f5_2 * (int64_t) g7_19; + int64_t f5g8_19 = f5 * (int64_t) g8_19; + int64_t f5g9_38 = f5_2 * (int64_t) g9_19; + int64_t f6g0 = f6 * (int64_t) g0; + int64_t f6g1 = f6 * (int64_t) g1; + int64_t f6g2 = f6 * (int64_t) g2; + int64_t f6g3 = f6 * (int64_t) g3; + int64_t f6g4_19 = f6 * (int64_t) g4_19; + int64_t f6g5_19 = f6 * (int64_t) g5_19; + int64_t f6g6_19 = f6 * (int64_t) g6_19; + int64_t f6g7_19 = f6 * (int64_t) g7_19; + int64_t f6g8_19 = f6 * (int64_t) g8_19; + int64_t f6g9_19 = f6 * (int64_t) g9_19; + int64_t f7g0 = f7 * (int64_t) g0; + int64_t f7g1_2 = f7_2 * (int64_t) g1; + int64_t f7g2 = f7 * (int64_t) g2; + int64_t f7g3_38 = f7_2 * (int64_t) g3_19; + int64_t f7g4_19 = f7 * (int64_t) g4_19; + int64_t f7g5_38 = f7_2 * (int64_t) g5_19; + int64_t f7g6_19 = f7 * (int64_t) g6_19; + int64_t f7g7_38 = f7_2 * (int64_t) g7_19; + int64_t f7g8_19 = f7 * (int64_t) g8_19; + int64_t f7g9_38 = f7_2 * (int64_t) g9_19; + int64_t f8g0 = f8 * (int64_t) g0; + int64_t f8g1 = f8 * (int64_t) g1; + int64_t f8g2_19 = f8 * (int64_t) g2_19; + int64_t f8g3_19 = f8 * (int64_t) g3_19; + int64_t f8g4_19 = f8 * (int64_t) g4_19; + int64_t f8g5_19 = f8 * (int64_t) g5_19; + int64_t f8g6_19 = f8 * (int64_t) g6_19; + int64_t f8g7_19 = f8 * (int64_t) g7_19; + int64_t f8g8_19 = f8 * (int64_t) g8_19; + int64_t f8g9_19 = f8 * (int64_t) g9_19; + int64_t f9g0 = f9 * (int64_t) g0; + int64_t f9g1_38 = f9_2 * (int64_t) g1_19; + int64_t f9g2_19 = f9 * (int64_t) g2_19; + int64_t f9g3_38 = f9_2 * (int64_t) g3_19; + int64_t f9g4_19 = f9 * (int64_t) g4_19; + int64_t f9g5_38 = f9_2 * (int64_t) g5_19; + int64_t f9g6_19 = f9 * (int64_t) g6_19; + int64_t f9g7_38 = f9_2 * (int64_t) g7_19; + int64_t f9g8_19 = f9 * (int64_t) g8_19; + int64_t f9g9_38 = f9_2 * (int64_t) g9_19; + int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38; + int64_t h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19; + int64_t h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38; + int64_t h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19; + int64_t h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38; + int64_t h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19; + int64_t h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 + f7g9_38 + f8g8_19 + f9g7_38; + int64_t h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 + f8g9_19 + f9g8_19; + int64_t h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 + f8g0 + f9g9_38; + int64_t h9 = f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0 ; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + + carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= shl64(carry1, 25); + carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= shl64(carry5, 25); + + carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= shl64(carry2, 26); + carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= shl64(carry6, 26); + + carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= shl64(carry3, 25); + carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= shl64(carry7, 25); + + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= shl64(carry8, 26); + + carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shl64(carry9, 25); + + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f * 121666 +Can overlap h with f. + +Preconditions: + |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_mul121666(fe h, fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int64_t h0 = f0 * (int64_t) 121666; + int64_t h1 = f1 * (int64_t) 121666; + int64_t h2 = f2 * (int64_t) 121666; + int64_t h3 = f3 * (int64_t) 121666; + int64_t h4 = f4 * (int64_t) 121666; + int64_t h5 = f5 * (int64_t) 121666; + int64_t h6 = f6 * (int64_t) 121666; + int64_t h7 = f7 * (int64_t) 121666; + int64_t h8 = f8 * (int64_t) 121666; + int64_t h9 = f9 * (int64_t) 121666; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + + carry9 = (h9 + (int64_t)(1 << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shl64(carry9, 25); + carry1 = (h1 + (int64_t)(1 << 24)) >> 25; + h2 += carry1; + h1 -= shl64(carry1, 25); + carry3 = (h3 + (int64_t)(1 << 24)) >> 25; + h4 += carry3; + h3 -= shl64(carry3, 25); + carry5 = (h5 + (int64_t)(1 << 24)) >> 25; + h6 += carry5; + h5 -= shl64(carry5, 25); + carry7 = (h7 + (int64_t)(1 << 24)) >> 25; + h8 += carry7; + h7 -= shl64(carry7, 25); + + carry0 = (h0 + (int64_t)(1 << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + carry2 = (h2 + (int64_t)(1 << 25)) >> 26; + h3 += carry2; + h2 -= shl64(carry2, 26); + carry4 = (h4 + (int64_t)(1 << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry6 = (h6 + (int64_t)(1 << 25)) >> 26; + h7 += carry6; + h6 -= shl64(carry6, 26); + carry8 = (h8 + (int64_t)(1 << 25)) >> 26; + h9 += carry8; + h8 -= shl64(carry8, 26); + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +/* +h = -f + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. +*/ + +void fe_neg(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t h0 = -f0; + int32_t h1 = -f1; + int32_t h2 = -f2; + int32_t h3 = -f3; + int32_t h4 = -f4; + int32_t h5 = -f5; + int32_t h6 = -f6; + int32_t h7 = -f7; + int32_t h8 = -f8; + int32_t h9 = -f9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + +void fe_pow22523(fe out, const fe z) { + fe t0; + fe t1; + fe t2; + int i; + fe_sq(t0, z); + + for(i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_sq(t1, t0); + + for(i = 1; i < 2; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, z, t1); + fe_mul(t0, t0, t1); + fe_sq(t0, t0); + + for(i = 1; i < 1; ++i) { + fe_sq(t0, t0); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for(i = 1; i < 5; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for(i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for(i = 1; i < 20; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for(i = 1; i < 10; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t1, t0); + + for(i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t1, t1, t0); + fe_sq(t2, t1); + + for(i = 1; i < 100; ++i) { + fe_sq(t2, t2); + } + + fe_mul(t1, t2, t1); + fe_sq(t1, t1); + + for(i = 1; i < 50; ++i) { + fe_sq(t1, t1); + } + + fe_mul(t0, t1, t0); + fe_sq(t0, t0); + + for(i = 1; i < 2; ++i) { + fe_sq(t0, t0); + } + + fe_mul(out, t0, z); + return; +} + + +/* +h = f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= shl64(carry1, 25); + carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= shl64(carry5, 25); + carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= shl64(carry2, 26); + carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= shl64(carry6, 26); + carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= shl64(carry3, 25); + carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= shl64(carry7, 25); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= shl64(carry8, 26); + carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shl64(carry9, 25); + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = 2 * f * f +Can overlap h with f. + +Preconditions: + |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc. + +Postconditions: + |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc. +*/ + +/* +See fe_mul.c for discussion of implementation strategy. +*/ + +void fe_sq2(fe h, const fe f) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t f0_2 = 2 * f0; + int32_t f1_2 = 2 * f1; + int32_t f2_2 = 2 * f2; + int32_t f3_2 = 2 * f3; + int32_t f4_2 = 2 * f4; + int32_t f5_2 = 2 * f5; + int32_t f6_2 = 2 * f6; + int32_t f7_2 = 2 * f7; + int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */ + int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */ + int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */ + int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */ + int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */ + int64_t f0f0 = f0 * (int64_t) f0; + int64_t f0f1_2 = f0_2 * (int64_t) f1; + int64_t f0f2_2 = f0_2 * (int64_t) f2; + int64_t f0f3_2 = f0_2 * (int64_t) f3; + int64_t f0f4_2 = f0_2 * (int64_t) f4; + int64_t f0f5_2 = f0_2 * (int64_t) f5; + int64_t f0f6_2 = f0_2 * (int64_t) f6; + int64_t f0f7_2 = f0_2 * (int64_t) f7; + int64_t f0f8_2 = f0_2 * (int64_t) f8; + int64_t f0f9_2 = f0_2 * (int64_t) f9; + int64_t f1f1_2 = f1_2 * (int64_t) f1; + int64_t f1f2_2 = f1_2 * (int64_t) f2; + int64_t f1f3_4 = f1_2 * (int64_t) f3_2; + int64_t f1f4_2 = f1_2 * (int64_t) f4; + int64_t f1f5_4 = f1_2 * (int64_t) f5_2; + int64_t f1f6_2 = f1_2 * (int64_t) f6; + int64_t f1f7_4 = f1_2 * (int64_t) f7_2; + int64_t f1f8_2 = f1_2 * (int64_t) f8; + int64_t f1f9_76 = f1_2 * (int64_t) f9_38; + int64_t f2f2 = f2 * (int64_t) f2; + int64_t f2f3_2 = f2_2 * (int64_t) f3; + int64_t f2f4_2 = f2_2 * (int64_t) f4; + int64_t f2f5_2 = f2_2 * (int64_t) f5; + int64_t f2f6_2 = f2_2 * (int64_t) f6; + int64_t f2f7_2 = f2_2 * (int64_t) f7; + int64_t f2f8_38 = f2_2 * (int64_t) f8_19; + int64_t f2f9_38 = f2 * (int64_t) f9_38; + int64_t f3f3_2 = f3_2 * (int64_t) f3; + int64_t f3f4_2 = f3_2 * (int64_t) f4; + int64_t f3f5_4 = f3_2 * (int64_t) f5_2; + int64_t f3f6_2 = f3_2 * (int64_t) f6; + int64_t f3f7_76 = f3_2 * (int64_t) f7_38; + int64_t f3f8_38 = f3_2 * (int64_t) f8_19; + int64_t f3f9_76 = f3_2 * (int64_t) f9_38; + int64_t f4f4 = f4 * (int64_t) f4; + int64_t f4f5_2 = f4_2 * (int64_t) f5; + int64_t f4f6_38 = f4_2 * (int64_t) f6_19; + int64_t f4f7_38 = f4 * (int64_t) f7_38; + int64_t f4f8_38 = f4_2 * (int64_t) f8_19; + int64_t f4f9_38 = f4 * (int64_t) f9_38; + int64_t f5f5_38 = f5 * (int64_t) f5_38; + int64_t f5f6_38 = f5_2 * (int64_t) f6_19; + int64_t f5f7_76 = f5_2 * (int64_t) f7_38; + int64_t f5f8_38 = f5_2 * (int64_t) f8_19; + int64_t f5f9_76 = f5_2 * (int64_t) f9_38; + int64_t f6f6_19 = f6 * (int64_t) f6_19; + int64_t f6f7_38 = f6 * (int64_t) f7_38; + int64_t f6f8_38 = f6_2 * (int64_t) f8_19; + int64_t f6f9_38 = f6 * (int64_t) f9_38; + int64_t f7f7_38 = f7 * (int64_t) f7_38; + int64_t f7f8_38 = f7_2 * (int64_t) f8_19; + int64_t f7f9_76 = f7_2 * (int64_t) f9_38; + int64_t f8f8_19 = f8 * (int64_t) f8_19; + int64_t f8f9_38 = f8 * (int64_t) f9_38; + int64_t f9f9_38 = f9 * (int64_t) f9_38; + int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38; + int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38; + int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19; + int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38; + int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38; + int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38; + int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19; + int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38; + int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38; + int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + h0 += h0; + h1 += h1; + h2 += h2; + h3 += h3; + h4 += h4; + h5 += h5; + h6 += h6; + h7 += h7; + h8 += h8; + h9 += h9; + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry1 = (h1 + (1L << 24)) >> 25; + h2 += carry1; + h1 -= shl64(carry1, 25); + carry5 = (h5 + (1L << 24)) >> 25; + h6 += carry5; + h5 -= shl64(carry5, 25); + carry2 = (h2 + (1L << 25)) >> 26; + h3 += carry2; + h2 -= shl64(carry2, 26); + carry6 = (h6 + (1L << 25)) >> 26; + h7 += carry6; + h6 -= shl64(carry6, 26); + carry3 = (h3 + (1L << 24)) >> 25; + h4 += carry3; + h3 -= shl64(carry3, 25); + carry7 = (h7 + (1L << 24)) >> 25; + h8 += carry7; + h7 -= shl64(carry7, 25); + carry4 = (h4 + (1L << 25)) >> 26; + h5 += carry4; + h4 -= shl64(carry4, 26); + carry8 = (h8 + (1L << 25)) >> 26; + h9 += carry8; + h8 -= shl64(carry8, 26); + carry9 = (h9 + (1L << 24)) >> 25; + h0 += carry9 * 19; + h9 -= shl64(carry9, 25); + carry0 = (h0 + (1L << 25)) >> 26; + h1 += carry0; + h0 -= shl64(carry0, 26); + h[0] = (int32_t) h0; + h[1] = (int32_t) h1; + h[2] = (int32_t) h2; + h[3] = (int32_t) h3; + h[4] = (int32_t) h4; + h[5] = (int32_t) h5; + h[6] = (int32_t) h6; + h[7] = (int32_t) h7; + h[8] = (int32_t) h8; + h[9] = (int32_t) h9; +} + + +/* +h = f - g +Can overlap h with f or g. + +Preconditions: + |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc. + +Postconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. +*/ + +void fe_sub(fe h, const fe f, const fe g) { + int32_t f0 = f[0]; + int32_t f1 = f[1]; + int32_t f2 = f[2]; + int32_t f3 = f[3]; + int32_t f4 = f[4]; + int32_t f5 = f[5]; + int32_t f6 = f[6]; + int32_t f7 = f[7]; + int32_t f8 = f[8]; + int32_t f9 = f[9]; + int32_t g0 = g[0]; + int32_t g1 = g[1]; + int32_t g2 = g[2]; + int32_t g3 = g[3]; + int32_t g4 = g[4]; + int32_t g5 = g[5]; + int32_t g6 = g[6]; + int32_t g7 = g[7]; + int32_t g8 = g[8]; + int32_t g9 = g[9]; + int32_t h0 = f0 - g0; + int32_t h1 = f1 - g1; + int32_t h2 = f2 - g2; + int32_t h3 = f3 - g3; + int32_t h4 = f4 - g4; + int32_t h5 = f5 - g5; + int32_t h6 = f6 - g6; + int32_t h7 = f7 - g7; + int32_t h8 = f8 - g8; + int32_t h9 = f9 - g9; + + h[0] = h0; + h[1] = h1; + h[2] = h2; + h[3] = h3; + h[4] = h4; + h[5] = h5; + h[6] = h6; + h[7] = h7; + h[8] = h8; + h[9] = h9; +} + + + +/* +Preconditions: + |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. + +Write p=2^255-19; q=floor(h/p). +Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))). + +Proof: + Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4. + Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4. + + Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9). + Then 0> 25; + q = (h0 + q) >> 26; + q = (h1 + q) >> 25; + q = (h2 + q) >> 26; + q = (h3 + q) >> 25; + q = (h4 + q) >> 26; + q = (h5 + q) >> 25; + q = (h6 + q) >> 26; + q = (h7 + q) >> 25; + q = (h8 + q) >> 26; + q = (h9 + q) >> 25; + /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */ + h0 += 19 * q; + /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */ + carry0 = h0 >> 26; + h1 += carry0; + h0 -= shl32(carry0, 26); + carry1 = h1 >> 25; + h2 += carry1; + h1 -= shl32(carry1, 25); + carry2 = h2 >> 26; + h3 += carry2; + h2 -= shl32(carry2, 26); + carry3 = h3 >> 25; + h4 += carry3; + h3 -= shl32(carry3, 25); + carry4 = h4 >> 26; + h5 += carry4; + h4 -= shl32(carry4, 26); + carry5 = h5 >> 25; + h6 += carry5; + h5 -= shl32(carry5, 25); + carry6 = h6 >> 26; + h7 += carry6; + h6 -= shl32(carry6, 26); + carry7 = h7 >> 25; + h8 += carry7; + h7 -= shl32(carry7, 25); + carry8 = h8 >> 26; + h9 += carry8; + h8 -= shl32(carry8, 26); + carry9 = h9 >> 25; + h9 -= shl32(carry9, 25); + + /* h10 = carry9 */ + /* + Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20. + Have h0+...+2^230 h9 between 0 and 2^255-1; + evidently 2^255 h10-2^255 q = 0. + Goal: Output h0+...+2^230 h9. + */ + s[0] = (unsigned char)(h0 >> 0); + s[1] = (unsigned char)(h0 >> 8); + s[2] = (unsigned char)(h0 >> 16); + s[3] = (unsigned char)((h0 >> 24) | shl32(h1, 2)); + s[4] = (unsigned char)(h1 >> 6); + s[5] = (unsigned char)(h1 >> 14); + s[6] = (unsigned char)((h1 >> 22) | shl32(h2, 3)); + s[7] = (unsigned char)(h2 >> 5); + s[8] = (unsigned char)(h2 >> 13); + s[9] = (unsigned char)((h2 >> 21) | shl32(h3, 5)); + s[10] = (unsigned char)(h3 >> 3); + s[11] = (unsigned char)(h3 >> 11); + s[12] = (unsigned char)((h3 >> 19) | shl32(h4, 6)); + s[13] = (unsigned char)(h4 >> 2); + s[14] = (unsigned char)(h4 >> 10); + s[15] = (unsigned char)(h4 >> 18); + s[16] = (unsigned char)(h5 >> 0); + s[17] = (unsigned char)(h5 >> 8); + s[18] = (unsigned char)(h5 >> 16); + s[19] = (unsigned char)((h5 >> 24) | shl32(h6, 1)); + s[20] = (unsigned char)(h6 >> 7); + s[21] = (unsigned char)(h6 >> 15); + s[22] = (unsigned char)((h6 >> 23) | shl32(h7, 3)); + s[23] = (unsigned char)(h7 >> 5); + s[24] = (unsigned char)(h7 >> 13); + s[25] = (unsigned char)((h7 >> 21) | shl32(h8, 4)); + s[26] = (unsigned char)(h8 >> 4); + s[27] = (unsigned char)(h8 >> 12); + s[28] = (unsigned char)((h8 >> 20) | shl32(h9, 6)); + s[29] = (unsigned char)(h9 >> 2); + s[30] = (unsigned char)(h9 >> 10); + s[31] = (unsigned char)(h9 >> 18); +} diff --git a/src/ed25519/fe.h b/src/ed25519/fe.h new file mode 100644 index 0000000..b4b62d2 --- /dev/null +++ b/src/ed25519/fe.h @@ -0,0 +1,41 @@ +#ifndef FE_H +#define FE_H + +#include "fixedint.h" + + +/* + fe means field element. + Here the field is \Z/(2^255-19). + An element t, entries t[0]...t[9], represents the integer + t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9]. + Bounds on each t[i] vary depending on context. +*/ + + +typedef int32_t fe[10]; + + +void fe_0(fe h); +void fe_1(fe h); + +void fe_frombytes(fe h, const unsigned char *s); +void fe_tobytes(unsigned char *s, const fe h); + +void fe_copy(fe h, const fe f); +int fe_isnegative(const fe f); +int fe_isnonzero(const fe f); +void fe_cmov(fe f, const fe g, unsigned int b); +void fe_cswap(fe f, fe g, unsigned int b); + +void fe_neg(fe h, const fe f); +void fe_add(fe h, const fe f, const fe g); +void fe_invert(fe out, const fe z); +void fe_sq(fe h, const fe f); +void fe_sq2(fe h, const fe f); +void fe_mul(fe h, const fe f, const fe g); +void fe_mul121666(fe h, fe f); +void fe_pow22523(fe out, const fe z); +void fe_sub(fe h, const fe f, const fe g); + +#endif diff --git a/src/ed25519/fixedint.h b/src/ed25519/fixedint.h new file mode 100644 index 0000000..af031e9 --- /dev/null +++ b/src/ed25519/fixedint.h @@ -0,0 +1,91 @@ +#ifndef TINC_FIXEDINT_H +#define TINC_FIXEDINT_H + +/* + Portable header to provide the 32 and 64 bits type. + + Not a compatible replacement for , do not blindly use it as such. +*/ + +#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED) +#include +#define FIXEDINT_H_INCLUDED + +#if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C) +#include +#define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) +#endif +#endif + + +#ifndef FIXEDINT_H_INCLUDED +#define FIXEDINT_H_INCLUDED + +/* (u)int32_t */ +#ifndef uint32_t +#if (ULONG_MAX == 0xffffffffUL) +typedef unsigned long uint32_t; +#elif (UINT_MAX == 0xffffffffUL) +typedef unsigned int uint32_t; +#elif (USHRT_MAX == 0xffffffffUL) +typedef unsigned short uint32_t; +#endif +#endif + + +#ifndef int32_t +#if (LONG_MAX == 0x7fffffffL) +typedef signed long int32_t; +#elif (INT_MAX == 0x7fffffffL) +typedef signed int int32_t; +#elif (SHRT_MAX == 0x7fffffffL) +typedef signed short int32_t; +#endif +#endif + + +/* (u)int64_t */ +#if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L) +typedef long long int64_t; +typedef unsigned long long uint64_t; + +#define UINT64_C(v) v ##ULL +#define INT64_C(v) v ##LL +#elif defined(__GNUC__) +__extension__ typedef long long int64_t; +__extension__ typedef unsigned long long uint64_t; + +#define UINT64_C(v) v ##ULL +#define INT64_C(v) v ##LL +#elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC) +typedef long long int64_t; +typedef unsigned long long uint64_t; + +#define UINT64_C(v) v ##ULL +#define INT64_C(v) v ##LL +#elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC) +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#define UINT64_C(v) v ##UI64 +#define INT64_C(v) v ##I64 +#endif +#endif + +static inline unsigned char shlu8(unsigned char a, uint32_t b) { + return a << b; +} + +static inline int32_t shl32(uint32_t a, uint32_t b) { + return a << b; +} + +static inline int64_t shl64(uint64_t a, uint32_t b) { + return a << b; +} + +static inline uint64_t shlu64(uint64_t a, uint32_t b) { + return a << b; +} + +#endif diff --git a/src/ed25519/ge.c b/src/ed25519/ge.c new file mode 100644 index 0000000..ca8c39b --- /dev/null +++ b/src/ed25519/ge.c @@ -0,0 +1,467 @@ +#include "ge.h" +#include "precomp_data.h" + + +/* +r = p + q +*/ + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YplusX); + fe_mul(r->Y, r->Y, q->YminusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +static void slide(signed char *r, const unsigned char *a) { + int i; + int b; + int k; + + for(i = 0; i < 256; ++i) { + r[i] = 1 & (a[i >> 3] >> (i & 7)); + } + + for(i = 0; i < 256; ++i) + if(r[i]) { + for(b = 1; b <= 6 && i + b < 256; ++b) { + if(r[i + b]) { + if(r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if(r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + + for(k = i + b; k < 256; ++k) { + if(!r[k]) { + r[k] = 1; + break; + } + + r[k] = 0; + } + } else { + break; + } + } + } + } +} + +/* +r = a * A + b * B +where a = a[0]+256*a[1]+...+256^31 a[31]. +and b = b[0]+256*b[1]+...+256^31 b[31]. +B is the Ed25519 base point (x,4/5) with x positive. +*/ + +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */ + ge_p1p1 t; + ge_p3 u; + ge_p3 A2; + int i; + slide(aslide, a); + slide(bslide, b); + ge_p3_to_cached(&Ai[0], A); + ge_p3_dbl(&t, A); + ge_p1p1_to_p3(&A2, &t); + ge_add(&t, &A2, &Ai[0]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[1], &u); + ge_add(&t, &A2, &Ai[1]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[2], &u); + ge_add(&t, &A2, &Ai[2]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[3], &u); + ge_add(&t, &A2, &Ai[3]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[4], &u); + ge_add(&t, &A2, &Ai[4]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[5], &u); + ge_add(&t, &A2, &Ai[5]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[6], &u); + ge_add(&t, &A2, &Ai[6]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[7], &u); + ge_p2_0(r); + + for(i = 255; i >= 0; --i) { + if(aslide[i] || bslide[i]) { + break; + } + } + + for(; i >= 0; --i) { + ge_p2_dbl(&t, r); + + if(aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i] / 2]); + } else if(aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]); + } + + if(bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &Bi[bslide[i] / 2]); + } else if(bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]); + } + + ge_p1p1_to_p2(r, &t); + } +} + + +static const fe d = { + -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116 + }; + +static const fe sqrtm1 = { + -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482 + }; + +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) { + fe u; + fe v; + fe v3; + fe vxx; + fe check; + fe_frombytes(h->Y, s); + fe_1(h->Z); + fe_sq(u, h->Y); + fe_mul(v, u, d); + fe_sub(u, u, h->Z); /* u = y^2-1 */ + fe_add(v, v, h->Z); /* v = dy^2+1 */ + fe_sq(v3, v); + fe_mul(v3, v3, v); /* v3 = v^3 */ + fe_sq(h->X, v3); + fe_mul(h->X, h->X, v); + fe_mul(h->X, h->X, u); /* x = uv^7 */ + fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */ + fe_mul(h->X, h->X, v3); + fe_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */ + fe_sq(vxx, h->X); + fe_mul(vxx, vxx, v); + fe_sub(check, vxx, u); /* vx^2-u */ + + if(fe_isnonzero(check)) { + fe_add(check, vxx, u); /* vx^2+u */ + + if(fe_isnonzero(check)) { + return -1; + } + + fe_mul(h->X, h->X, sqrtm1); + } + + if(fe_isnegative(h->X) == (s[31] >> 7)) { + fe_neg(h->X, h->X); + } + + fe_mul(h->T, h->X, h->Y); + return 0; +} + + +/* +r = p + q +*/ + +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yplusx); + fe_mul(r->Y, r->Y, q->yminusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_add(r->Z, t0, r->T); + fe_sub(r->T, t0, r->T); +} + + +/* +r = p - q +*/ + +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->yminusx); + fe_mul(r->Y, r->Y, q->yplusx); + fe_mul(r->T, q->xy2d, p->T); + fe_add(t0, p->Z, p->Z); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +/* +r = p +*/ + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); +} + + + +/* +r = p +*/ + +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) { + fe_mul(r->X, p->X, p->T); + fe_mul(r->Y, p->Y, p->Z); + fe_mul(r->Z, p->Z, p->T); + fe_mul(r->T, p->X, p->Y); +} + + +void ge_p2_0(ge_p2 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); +} + + + +/* +r = 2 * p +*/ + +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { + fe t0; + + fe_sq(r->X, p->X); + fe_sq(r->Z, p->Y); + fe_sq2(r->T, p->Z); + fe_add(r->Y, p->X, p->Y); + fe_sq(t0, r->Y); + fe_add(r->Y, r->Z, r->X); + fe_sub(r->Z, r->Z, r->X); + fe_sub(r->X, t0, r->Y); + fe_sub(r->T, r->T, r->Z); +} + + +void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + + +/* +r = 2 * p +*/ + +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { + ge_p2 q; + ge_p3_to_p2(&q, p); + ge_p2_dbl(r, &q); +} + + + +/* +r = p +*/ + +static const fe d2 = { + -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 + }; + +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) { + fe_add(r->YplusX, p->Y, p->X); + fe_sub(r->YminusX, p->Y, p->X); + fe_copy(r->Z, p->Z); + fe_mul(r->T2d, p->T, d2); +} + + +/* +r = p +*/ + +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { + fe_copy(r->X, p->X); + fe_copy(r->Y, p->Y); + fe_copy(r->Z, p->Z); +} + + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} + + +static unsigned char equal(signed char b, signed char c) { + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint64_t y = x; /* 0: yes; 1..255: no */ + y -= 1; /* large: yes; 0..254: no */ + y >>= 63; /* 1: yes; 0: no */ + return (unsigned char) y; +} + +static unsigned char negative(signed char b) { + uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + x >>= 63; /* 1: yes; 0: no */ + return (unsigned char) x; +} + +static void cmov(ge_precomp *t, ge_precomp *u, unsigned char b) { + fe_cmov(t->yplusx, u->yplusx, b); + fe_cmov(t->yminusx, u->yminusx, b); + fe_cmov(t->xy2d, u->xy2d, b); +} + + +static void select(ge_precomp *t, int pos, signed char b) { + ge_precomp minust; + unsigned char bnegative = negative(b); + unsigned char babs = b - shlu8(((-bnegative) & b), 1); + fe_1(t->yplusx); + fe_1(t->yminusx); + fe_0(t->xy2d); + cmov(t, &base[pos][0], equal(babs, 1)); + cmov(t, &base[pos][1], equal(babs, 2)); + cmov(t, &base[pos][2], equal(babs, 3)); + cmov(t, &base[pos][3], equal(babs, 4)); + cmov(t, &base[pos][4], equal(babs, 5)); + cmov(t, &base[pos][5], equal(babs, 6)); + cmov(t, &base[pos][6], equal(babs, 7)); + cmov(t, &base[pos][7], equal(babs, 8)); + fe_copy(minust.yplusx, t->yminusx); + fe_copy(minust.yminusx, t->yplusx); + fe_neg(minust.xy2d, t->xy2d); + cmov(t, &minust, bnegative); +} + +/* +h = a * B +where a = a[0]+256*a[1]+...+256^31 a[31] +B is the Ed25519 base point (x,4/5) with x positive. + +Preconditions: + a[31] <= 127 +*/ + +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { + signed char e[64]; + signed char carry; + ge_p1p1 r; + ge_p2 s; + ge_precomp t; + int i; + + for(i = 0; i < 32; ++i) { + e[2 * i + 0] = (a[i] >> 0) & 15; + e[2 * i + 1] = (a[i] >> 4) & 15; + } + + /* each e[i] is between 0 and 15 */ + /* e[63] is between 0 and 7 */ + carry = 0; + + for(i = 0; i < 63; ++i) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= shl32(carry, 4); + } + + e[63] += carry; + /* each e[i] is between -8 and 8 */ + ge_p3_0(h); + + for(i = 1; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } + + ge_p3_dbl(&r, h); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p2(&s, &r); + ge_p2_dbl(&r, &s); + ge_p1p1_to_p3(h, &r); + + for(i = 0; i < 64; i += 2) { + select(&t, i / 2, e[i]); + ge_madd(&r, h, &t); + ge_p1p1_to_p3(h, &r); + } +} + + +/* +r = p - q +*/ + +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { + fe t0; + + fe_add(r->X, p->Y, p->X); + fe_sub(r->Y, p->Y, p->X); + fe_mul(r->Z, r->X, q->YminusX); + fe_mul(r->Y, r->Y, q->YplusX); + fe_mul(r->T, q->T2d, p->T); + fe_mul(r->X, p->Z, q->Z); + fe_add(t0, r->X, r->X); + fe_sub(r->X, r->Z, r->Y); + fe_add(r->Y, r->Z, r->Y); + fe_sub(r->Z, t0, r->T); + fe_add(r->T, t0, r->T); +} + + +void ge_tobytes(unsigned char *s, const ge_p2 *h) { + fe recip; + fe x; + fe y; + fe_invert(recip, h->Z); + fe_mul(x, h->X, recip); + fe_mul(y, h->Y, recip); + fe_tobytes(s, y); + s[31] ^= fe_isnegative(x) << 7; +} diff --git a/src/ed25519/ge.h b/src/ed25519/ge.h new file mode 100644 index 0000000..9cc3b98 --- /dev/null +++ b/src/ed25519/ge.h @@ -0,0 +1,74 @@ +#ifndef GE_H +#define GE_H + +#include "fe.h" + + +/* +ge means group element. + +Here the group is the set of pairs (x,y) of field elements (see fe.h) +satisfying -x^2 + y^2 = 1 + d x^2y^2 +where d = -121665/121666. + +Representations: + ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z + ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT + ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + ge_precomp (Duif): (y+x,y-x,2dxy) +*/ + +typedef struct { + fe X; + fe Y; + fe Z; +} ge_p2; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p3; + +typedef struct { + fe X; + fe Y; + fe Z; + fe T; +} ge_p1p1; + +typedef struct { + fe yplusx; + fe yminusx; + fe xy2d; +} ge_precomp; + +typedef struct { + fe YplusX; + fe YminusX; + fe Z; + fe T2d; +} ge_cached; + +void ge_p3_tobytes(unsigned char *s, const ge_p3 *h); +void ge_tobytes(unsigned char *s, const ge_p2 *h); +int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s); + +void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b); +void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q); +void ge_scalarmult_base(ge_p3 *h, const unsigned char *a); + +void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p); +void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p); +void ge_p2_0(ge_p2 *h); +void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p); +void ge_p3_0(ge_p3 *h); +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); +void ge_p3_to_cached(ge_cached *r, const ge_p3 *p); +void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p); + +#endif diff --git a/src/ed25519/key_exchange.c b/src/ed25519/key_exchange.c new file mode 100644 index 0000000..9ace86b --- /dev/null +++ b/src/ed25519/key_exchange.c @@ -0,0 +1,80 @@ +#include "ed25519.h" +#include "fe.h" + +void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) { + unsigned char e[32]; + unsigned int i; + + fe x1; + fe x2; + fe z2; + fe x3; + fe z3; + fe tmp0; + fe tmp1; + + int pos; + unsigned int swap; + unsigned int b; + + /* copy the private key and make sure it's valid */ + for(i = 0; i < 32; ++i) { + e[i] = private_key[i]; + } + + e[0] &= 248; + e[31] &= 63; + e[31] |= 64; + + /* unpack the public key and convert edwards to montgomery */ + /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */ + fe_frombytes(x1, public_key); + fe_1(tmp1); + fe_add(tmp0, x1, tmp1); + fe_sub(tmp1, tmp1, x1); + fe_invert(tmp1, tmp1); + fe_mul(x1, tmp0, tmp1); + + fe_1(x2); + fe_0(z2); + fe_copy(x3, x1); + fe_1(z3); + + swap = 0; + + for(pos = 254; pos >= 0; --pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + swap ^= b; + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + swap = b; + + /* from montgomery.h */ + fe_sub(tmp0, x3, z3); + fe_sub(tmp1, x2, z2); + fe_add(x2, x2, z2); + fe_add(z2, x3, z3); + fe_mul(z3, tmp0, x2); + fe_mul(z2, z2, tmp1); + fe_sq(tmp0, tmp1); + fe_sq(tmp1, x2); + fe_add(x3, z3, z2); + fe_sub(z2, z3, z2); + fe_mul(x2, tmp1, tmp0); + fe_sub(tmp1, tmp1, tmp0); + fe_sq(z2, z2); + fe_mul121666(z3, tmp1); + fe_sq(x3, x3); + fe_add(tmp0, tmp0, z3); + fe_mul(z3, x1, z2); + fe_mul(z2, tmp1, tmp0); + } + + fe_cswap(x2, x3, swap); + fe_cswap(z2, z3, swap); + + fe_invert(z2, z2); + fe_mul(x2, x2, z2); + fe_tobytes(shared_secret, x2); +} diff --git a/src/ed25519/keypair.c b/src/ed25519/keypair.c new file mode 100644 index 0000000..71ec4d7 --- /dev/null +++ b/src/ed25519/keypair.c @@ -0,0 +1,16 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" + + +void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) { + ge_p3 A; + + sha512(seed, 32, private_key); + private_key[0] &= 248; + private_key[31] &= 63; + private_key[31] |= 64; + + ge_scalarmult_base(&A, private_key); + ge_p3_tobytes(public_key, &A); +} diff --git a/src/ed25519/precomp_data.h b/src/ed25519/precomp_data.h new file mode 100644 index 0000000..7a29302 --- /dev/null +++ b/src/ed25519/precomp_data.h @@ -0,0 +1,1391 @@ +static ge_precomp Bi[8] = { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 }, + { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 }, + { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 }, + }, + { + { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 }, + { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 }, + { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 }, + }, + { + { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 }, + { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 }, + { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 }, + }, + { + { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 }, + { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 }, + { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 }, + }, +}; + + +/* base[i][j] = (j+1)*256^i*B */ +static ge_precomp base[32][8] = { + { + { + { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 }, + { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 }, + { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }, + }, + { + { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 }, + { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 }, + { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }, + }, + { + { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 }, + { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 }, + { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }, + }, + { + { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 }, + { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 }, + { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }, + }, + { + { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 }, + { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 }, + { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }, + }, + { + { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 }, + { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 }, + { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }, + }, + { + { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 }, + { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 }, + { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }, + }, + { + { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 }, + { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 }, + { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }, + }, + }, + { + { + { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 }, + { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 }, + { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }, + }, + { + { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 }, + { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 }, + { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }, + }, + { + { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 }, + { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 }, + { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }, + }, + { + { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 }, + { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 }, + { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }, + }, + { + { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 }, + { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 }, + { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }, + }, + { + { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 }, + { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 }, + { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }, + }, + { + { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 }, + { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 }, + { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }, + }, + { + { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 }, + { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 }, + { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }, + }, + }, + { + { + { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 }, + { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 }, + { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }, + }, + { + { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 }, + { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 }, + { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }, + }, + { + { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 }, + { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 }, + { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }, + }, + { + { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 }, + { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 }, + { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }, + }, + { + { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 }, + { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 }, + { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }, + }, + { + { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 }, + { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 }, + { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }, + }, + { + { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 }, + { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 }, + { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }, + }, + { + { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 }, + { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 }, + { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }, + }, + }, + { + { + { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 }, + { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 }, + { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }, + }, + { + { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 }, + { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 }, + { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }, + }, + { + { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 }, + { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 }, + { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }, + }, + { + { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 }, + { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 }, + { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }, + }, + { + { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 }, + { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 }, + { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }, + }, + { + { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 }, + { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 }, + { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }, + }, + { + { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 }, + { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 }, + { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }, + }, + { + { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 }, + { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 }, + { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }, + }, + }, + { + { + { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 }, + { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 }, + { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }, + }, + { + { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 }, + { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 }, + { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }, + }, + { + { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 }, + { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 }, + { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }, + }, + { + { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 }, + { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 }, + { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }, + }, + { + { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 }, + { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 }, + { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }, + }, + { + { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 }, + { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 }, + { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }, + }, + { + { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 }, + { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 }, + { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }, + }, + { + { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 }, + { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 }, + { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }, + }, + }, + { + { + { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 }, + { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 }, + { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }, + }, + { + { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 }, + { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 }, + { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }, + }, + { + { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 }, + { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 }, + { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }, + }, + { + { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 }, + { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 }, + { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }, + }, + { + { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 }, + { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 }, + { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }, + }, + { + { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 }, + { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 }, + { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }, + }, + { + { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 }, + { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 }, + { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }, + }, + { + { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 }, + { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 }, + { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }, + }, + }, + { + { + { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 }, + { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 }, + { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }, + }, + { + { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 }, + { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 }, + { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }, + }, + { + { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 }, + { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 }, + { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }, + }, + { + { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 }, + { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 }, + { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }, + }, + { + { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 }, + { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 }, + { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }, + }, + { + { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 }, + { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 }, + { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }, + }, + { + { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 }, + { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 }, + { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }, + }, + { + { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 }, + { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 }, + { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }, + }, + }, + { + { + { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 }, + { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 }, + { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }, + }, + { + { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 }, + { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 }, + { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }, + }, + { + { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 }, + { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 }, + { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }, + }, + { + { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 }, + { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 }, + { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }, + }, + { + { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 }, + { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 }, + { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }, + }, + { + { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 }, + { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 }, + { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }, + }, + { + { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 }, + { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 }, + { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }, + }, + { + { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 }, + { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 }, + { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }, + }, + }, + { + { + { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 }, + { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 }, + { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }, + }, + { + { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 }, + { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 }, + { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }, + }, + { + { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 }, + { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 }, + { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }, + }, + { + { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 }, + { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 }, + { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }, + }, + { + { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 }, + { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 }, + { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }, + }, + { + { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 }, + { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 }, + { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }, + }, + { + { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 }, + { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 }, + { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }, + }, + { + { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 }, + { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 }, + { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }, + }, + }, + { + { + { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 }, + { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 }, + { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }, + }, + { + { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 }, + { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 }, + { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }, + }, + { + { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 }, + { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 }, + { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }, + }, + { + { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 }, + { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 }, + { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }, + }, + { + { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 }, + { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 }, + { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }, + }, + { + { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 }, + { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 }, + { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }, + }, + { + { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 }, + { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 }, + { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }, + }, + { + { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 }, + { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 }, + { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }, + }, + }, + { + { + { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 }, + { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 }, + { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }, + }, + { + { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 }, + { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 }, + { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }, + }, + { + { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 }, + { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 }, + { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }, + }, + { + { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 }, + { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 }, + { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }, + }, + { + { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 }, + { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 }, + { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }, + }, + { + { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 }, + { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 }, + { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }, + }, + { + { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 }, + { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 }, + { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }, + }, + { + { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 }, + { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 }, + { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }, + }, + }, + { + { + { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 }, + { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 }, + { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }, + }, + { + { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 }, + { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 }, + { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }, + }, + { + { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 }, + { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 }, + { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }, + }, + { + { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 }, + { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 }, + { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }, + }, + { + { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 }, + { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 }, + { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }, + }, + { + { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 }, + { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 }, + { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }, + }, + { + { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 }, + { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 }, + { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }, + }, + { + { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 }, + { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 }, + { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }, + }, + }, + { + { + { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 }, + { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 }, + { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }, + }, + { + { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 }, + { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 }, + { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }, + }, + { + { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 }, + { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 }, + { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }, + }, + { + { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 }, + { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 }, + { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }, + }, + { + { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 }, + { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 }, + { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }, + }, + { + { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 }, + { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 }, + { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }, + }, + { + { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 }, + { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 }, + { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }, + }, + { + { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 }, + { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 }, + { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }, + }, + }, + { + { + { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 }, + { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 }, + { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }, + }, + { + { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 }, + { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 }, + { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }, + }, + { + { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 }, + { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 }, + { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }, + }, + { + { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 }, + { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 }, + { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }, + }, + { + { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 }, + { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 }, + { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }, + }, + { + { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 }, + { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 }, + { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }, + }, + { + { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 }, + { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 }, + { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }, + }, + { + { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 }, + { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 }, + { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }, + }, + }, + { + { + { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 }, + { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 }, + { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }, + }, + { + { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 }, + { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 }, + { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }, + }, + { + { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 }, + { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 }, + { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }, + }, + { + { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 }, + { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 }, + { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }, + }, + { + { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 }, + { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 }, + { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }, + }, + { + { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 }, + { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 }, + { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }, + }, + { + { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 }, + { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 }, + { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }, + }, + { + { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 }, + { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 }, + { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }, + }, + }, + { + { + { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 }, + { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 }, + { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }, + }, + { + { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 }, + { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 }, + { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }, + }, + { + { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 }, + { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 }, + { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }, + }, + { + { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 }, + { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 }, + { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }, + }, + { + { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 }, + { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 }, + { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }, + }, + { + { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 }, + { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 }, + { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }, + }, + { + { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 }, + { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 }, + { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }, + }, + { + { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 }, + { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 }, + { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }, + }, + }, + { + { + { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 }, + { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 }, + { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }, + }, + { + { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 }, + { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 }, + { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }, + }, + { + { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 }, + { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 }, + { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }, + }, + { + { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 }, + { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 }, + { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }, + }, + { + { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 }, + { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 }, + { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }, + }, + { + { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 }, + { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 }, + { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }, + }, + { + { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 }, + { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 }, + { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }, + }, + { + { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 }, + { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 }, + { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }, + }, + }, + { + { + { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 }, + { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 }, + { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }, + }, + { + { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 }, + { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 }, + { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }, + }, + { + { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 }, + { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 }, + { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }, + }, + { + { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 }, + { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 }, + { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }, + }, + { + { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 }, + { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 }, + { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }, + }, + { + { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 }, + { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 }, + { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }, + }, + { + { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 }, + { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 }, + { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }, + }, + { + { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 }, + { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 }, + { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }, + }, + }, + { + { + { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 }, + { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 }, + { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }, + }, + { + { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 }, + { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 }, + { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }, + }, + { + { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 }, + { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 }, + { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }, + }, + { + { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 }, + { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 }, + { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }, + }, + { + { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 }, + { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 }, + { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }, + }, + { + { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 }, + { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 }, + { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }, + }, + { + { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 }, + { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 }, + { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }, + }, + { + { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 }, + { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 }, + { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }, + }, + }, + { + { + { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 }, + { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 }, + { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }, + }, + { + { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 }, + { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 }, + { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }, + }, + { + { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 }, + { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 }, + { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }, + }, + { + { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 }, + { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 }, + { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }, + }, + { + { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 }, + { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 }, + { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }, + }, + { + { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 }, + { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 }, + { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }, + }, + { + { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 }, + { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 }, + { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }, + }, + { + { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 }, + { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 }, + { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }, + }, + }, + { + { + { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 }, + { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 }, + { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }, + }, + { + { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 }, + { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 }, + { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }, + }, + { + { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 }, + { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 }, + { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }, + }, + { + { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 }, + { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 }, + { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }, + }, + { + { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 }, + { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 }, + { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }, + }, + { + { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 }, + { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 }, + { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }, + }, + { + { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 }, + { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 }, + { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }, + }, + { + { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 }, + { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 }, + { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }, + }, + }, + { + { + { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 }, + { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 }, + { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }, + }, + { + { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 }, + { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 }, + { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }, + }, + { + { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 }, + { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 }, + { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }, + }, + { + { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 }, + { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 }, + { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }, + }, + { + { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 }, + { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 }, + { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }, + }, + { + { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 }, + { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 }, + { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }, + }, + { + { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 }, + { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 }, + { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }, + }, + { + { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 }, + { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 }, + { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }, + }, + }, + { + { + { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 }, + { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 }, + { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }, + }, + { + { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 }, + { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 }, + { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }, + }, + { + { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 }, + { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 }, + { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }, + }, + { + { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 }, + { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 }, + { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }, + }, + { + { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 }, + { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 }, + { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }, + }, + { + { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 }, + { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 }, + { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }, + }, + { + { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 }, + { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 }, + { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }, + }, + { + { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 }, + { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 }, + { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }, + }, + }, + { + { + { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 }, + { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 }, + { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }, + }, + { + { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 }, + { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 }, + { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }, + }, + { + { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 }, + { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 }, + { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }, + }, + { + { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 }, + { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 }, + { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }, + }, + { + { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 }, + { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 }, + { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }, + }, + { + { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 }, + { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 }, + { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }, + }, + { + { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 }, + { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 }, + { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }, + }, + { + { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 }, + { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 }, + { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }, + }, + }, + { + { + { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 }, + { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 }, + { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }, + }, + { + { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 }, + { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 }, + { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }, + }, + { + { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 }, + { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 }, + { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }, + }, + { + { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 }, + { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 }, + { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }, + }, + { + { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 }, + { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 }, + { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }, + }, + { + { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 }, + { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 }, + { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }, + }, + { + { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 }, + { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 }, + { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }, + }, + { + { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 }, + { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 }, + { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }, + }, + }, + { + { + { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 }, + { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 }, + { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }, + }, + { + { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 }, + { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 }, + { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }, + }, + { + { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 }, + { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 }, + { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }, + }, + { + { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 }, + { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 }, + { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }, + }, + { + { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 }, + { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 }, + { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }, + }, + { + { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 }, + { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 }, + { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }, + }, + { + { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 }, + { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 }, + { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }, + }, + { + { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 }, + { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 }, + { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }, + }, + }, + { + { + { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 }, + { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 }, + { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }, + }, + { + { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 }, + { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 }, + { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }, + }, + { + { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 }, + { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 }, + { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }, + }, + { + { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 }, + { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 }, + { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }, + }, + { + { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 }, + { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 }, + { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }, + }, + { + { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 }, + { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 }, + { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }, + }, + { + { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 }, + { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 }, + { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }, + }, + { + { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 }, + { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 }, + { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }, + }, + }, + { + { + { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 }, + { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 }, + { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }, + }, + { + { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 }, + { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 }, + { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }, + }, + { + { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 }, + { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 }, + { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }, + }, + { + { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 }, + { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 }, + { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }, + }, + { + { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 }, + { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 }, + { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }, + }, + { + { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 }, + { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 }, + { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }, + }, + { + { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 }, + { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 }, + { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }, + }, + { + { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 }, + { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 }, + { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }, + }, + }, + { + { + { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 }, + { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 }, + { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }, + }, + { + { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 }, + { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 }, + { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }, + }, + { + { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 }, + { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 }, + { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }, + }, + { + { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 }, + { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 }, + { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }, + }, + { + { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 }, + { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 }, + { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }, + }, + { + { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 }, + { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 }, + { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }, + }, + { + { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 }, + { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 }, + { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }, + }, + { + { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 }, + { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 }, + { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }, + }, + }, + { + { + { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 }, + { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 }, + { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }, + }, + { + { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 }, + { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 }, + { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }, + }, + { + { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 }, + { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 }, + { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }, + }, + { + { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 }, + { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 }, + { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }, + }, + { + { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 }, + { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 }, + { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }, + }, + { + { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 }, + { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 }, + { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }, + }, + { + { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 }, + { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 }, + { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }, + }, + { + { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 }, + { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 }, + { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }, + }, + }, + { + { + { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 }, + { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 }, + { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }, + }, + { + { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 }, + { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 }, + { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }, + }, + { + { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 }, + { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 }, + { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }, + }, + { + { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 }, + { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 }, + { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }, + }, + { + { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 }, + { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 }, + { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }, + }, + { + { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 }, + { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 }, + { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }, + }, + { + { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 }, + { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 }, + { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }, + }, + { + { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 }, + { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 }, + { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }, + }, + }, + { + { + { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 }, + { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 }, + { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }, + }, + { + { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 }, + { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 }, + { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }, + }, + { + { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 }, + { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 }, + { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }, + }, + { + { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 }, + { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 }, + { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }, + }, + { + { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 }, + { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 }, + { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }, + }, + { + { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 }, + { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 }, + { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }, + }, + { + { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 }, + { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 }, + { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }, + }, + { + { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 }, + { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 }, + { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }, + }, + }, +}; diff --git a/src/ed25519/sc.c b/src/ed25519/sc.c new file mode 100644 index 0000000..ee54d99 --- /dev/null +++ b/src/ed25519/sc.c @@ -0,0 +1,785 @@ +#include "fixedint.h" +#include "sc.h" + +static uint64_t load_3(const unsigned char *in) { + uint64_t result; + + result = in[0]; + result |= shlu64(in[1], 8); + result |= shlu64(in[2], 16); + + return result; +} + +static uint64_t load_4(const unsigned char *in) { + uint64_t result; + + result = in[0]; + result |= shlu64(in[1], 8); + result |= shlu64(in[2], 16); + result |= shlu64(in[3], 24); + + return result; +} + +/* +Input: + s[0]+256*s[1]+...+256^63*s[63] = s + +Output: + s[0]+256*s[1]+...+256^31*s[31] = s mod l + where l = 2^252 + 27742317777372353535851937790883648493. + Overwrites s in place. +*/ + +void sc_reduce(unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = 2097151 & (load_4(s + 28) >> 7); + int64_t s12 = 2097151 & (load_4(s + 31) >> 4); + int64_t s13 = 2097151 & (load_3(s + 34) >> 1); + int64_t s14 = 2097151 & (load_4(s + 36) >> 6); + int64_t s15 = 2097151 & (load_3(s + 39) >> 3); + int64_t s16 = 2097151 & load_3(s + 42); + int64_t s17 = 2097151 & (load_4(s + 44) >> 5); + int64_t s18 = 2097151 & (load_3(s + 47) >> 2); + int64_t s19 = 2097151 & (load_4(s + 49) >> 7); + int64_t s20 = 2097151 & (load_4(s + 52) >> 4); + int64_t s21 = 2097151 & (load_3(s + 55) >> 1); + int64_t s22 = 2097151 & (load_4(s + 57) >> 6); + int64_t s23 = (load_4(s + 60) >> 3); + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shl64(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shl64(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shl64(carry16, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shl64(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shl64(carry15, 21); + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry11 = s11 >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + + s[0] = (unsigned char)(s0 >> 0); + s[1] = (unsigned char)(s0 >> 8); + s[2] = (unsigned char)((s0 >> 16) | shl64(s1, 5)); + s[3] = (unsigned char)(s1 >> 3); + s[4] = (unsigned char)(s1 >> 11); + s[5] = (unsigned char)((s1 >> 19) | shl64(s2, 2)); + s[6] = (unsigned char)(s2 >> 6); + s[7] = (unsigned char)((s2 >> 14) | shl64(s3, 7)); + s[8] = (unsigned char)(s3 >> 1); + s[9] = (unsigned char)(s3 >> 9); + s[10] = (unsigned char)((s3 >> 17) | shl64(s4, 4)); + s[11] = (unsigned char)(s4 >> 4); + s[12] = (unsigned char)(s4 >> 12); + s[13] = (unsigned char)((s4 >> 20) | shl64(s5, 1)); + s[14] = (unsigned char)(s5 >> 7); + s[15] = (unsigned char)((s5 >> 15) | shl64(s6, 6)); + s[16] = (unsigned char)(s6 >> 2); + s[17] = (unsigned char)(s6 >> 10); + s[18] = (unsigned char)((s6 >> 18) | shl64(s7, 3)); + s[19] = (unsigned char)(s7 >> 5); + s[20] = (unsigned char)(s7 >> 13); + s[21] = (unsigned char)(s8 >> 0); + s[22] = (unsigned char)(s8 >> 8); + s[23] = (unsigned char)((s8 >> 16) | shl64(s9, 5)); + s[24] = (unsigned char)(s9 >> 3); + s[25] = (unsigned char)(s9 >> 11); + s[26] = (unsigned char)((s9 >> 19) | shl64(s10, 2)); + s[27] = (unsigned char)(s10 >> 6); + s[28] = (unsigned char)((s10 >> 14) | shl64(s11, 7)); + s[29] = (unsigned char)(s11 >> 1); + s[30] = (unsigned char)(s11 >> 9); + s[31] = (unsigned char)(s11 >> 17); +} + + + +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shl64(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shl64(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shl64(carry16, 21); + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= shl64(carry18, 21); + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= shl64(carry20, 21); + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= shl64(carry22, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shl64(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shl64(carry15, 21); + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= shl64(carry17, 21); + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= shl64(carry19, 21); + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= shl64(carry21, 21); + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= shl64(carry12, 21); + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= shl64(carry14, 21); + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= shl64(carry16, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= shl64(carry13, 21); + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= shl64(carry15, 21); + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + carry11 = s11 >> 21; + s12 += carry11; + s11 -= shl64(carry11, 21); + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + carry0 = s0 >> 21; + s1 += carry0; + s0 -= shl64(carry0, 21); + carry1 = s1 >> 21; + s2 += carry1; + s1 -= shl64(carry1, 21); + carry2 = s2 >> 21; + s3 += carry2; + s2 -= shl64(carry2, 21); + carry3 = s3 >> 21; + s4 += carry3; + s3 -= shl64(carry3, 21); + carry4 = s4 >> 21; + s5 += carry4; + s4 -= shl64(carry4, 21); + carry5 = s5 >> 21; + s6 += carry5; + s5 -= shl64(carry5, 21); + carry6 = s6 >> 21; + s7 += carry6; + s6 -= shl64(carry6, 21); + carry7 = s7 >> 21; + s8 += carry7; + s7 -= shl64(carry7, 21); + carry8 = s8 >> 21; + s9 += carry8; + s8 -= shl64(carry8, 21); + carry9 = s9 >> 21; + s10 += carry9; + s9 -= shl64(carry9, 21); + carry10 = s10 >> 21; + s11 += carry10; + s10 -= shl64(carry10, 21); + + s[0] = (unsigned char)(s0 >> 0); + s[1] = (unsigned char)(s0 >> 8); + s[2] = (unsigned char)((s0 >> 16) | shl64(s1, 5)); + s[3] = (unsigned char)(s1 >> 3); + s[4] = (unsigned char)(s1 >> 11); + s[5] = (unsigned char)((s1 >> 19) | shl64(s2, 2)); + s[6] = (unsigned char)(s2 >> 6); + s[7] = (unsigned char)((s2 >> 14) | shl64(s3, 7)); + s[8] = (unsigned char)(s3 >> 1); + s[9] = (unsigned char)(s3 >> 9); + s[10] = (unsigned char)((s3 >> 17) | shl64(s4, 4)); + s[11] = (unsigned char)(s4 >> 4); + s[12] = (unsigned char)(s4 >> 12); + s[13] = (unsigned char)((s4 >> 20) | shl64(s5, 1)); + s[14] = (unsigned char)(s5 >> 7); + s[15] = (unsigned char)((s5 >> 15) | shl64(s6, 6)); + s[16] = (unsigned char)(s6 >> 2); + s[17] = (unsigned char)(s6 >> 10); + s[18] = (unsigned char)((s6 >> 18) | shl64(s7, 3)); + s[19] = (unsigned char)(s7 >> 5); + s[20] = (unsigned char)(s7 >> 13); + s[21] = (unsigned char)(s8 >> 0); + s[22] = (unsigned char)(s8 >> 8); + s[23] = (unsigned char)((s8 >> 16) | shl64(s9, 5)); + s[24] = (unsigned char)(s9 >> 3); + s[25] = (unsigned char)(s9 >> 11); + s[26] = (unsigned char)((s9 >> 19) | shl64(s10, 2)); + s[27] = (unsigned char)(s10 >> 6); + s[28] = (unsigned char)((s10 >> 14) | shl64(s11, 7)); + s[29] = (unsigned char)(s11 >> 1); + s[30] = (unsigned char)(s11 >> 9); + s[31] = (unsigned char)(s11 >> 17); +} diff --git a/src/ed25519/sc.h b/src/ed25519/sc.h new file mode 100644 index 0000000..e29e7fa --- /dev/null +++ b/src/ed25519/sc.h @@ -0,0 +1,12 @@ +#ifndef SC_H +#define SC_H + +/* +The set of scalars is \Z/l +where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_reduce(unsigned char *s); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); + +#endif diff --git a/src/ed25519/sha512.c b/src/ed25519/sha512.c new file mode 100644 index 0000000..9cdf94d --- /dev/null +++ b/src/ed25519/sha512.c @@ -0,0 +1,303 @@ +/* LibTomCrypt, modular cryptographic library -- Tom St Denis + * + * LibTomCrypt is a library that provides various cryptographic + * algorithms in a highly modular and flexible manner. + * + * The library is free for all purposes without any express + * guarantee it works. + * + * Tom St Denis, tomstdenis@gmail.com, http://libtom.org + */ + +#include "fixedint.h" +#include "sha512.h" + +/* the K array */ +static const uint64_t K[80] = { + UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), + UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc), + UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), + UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118), + UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), + UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2), + UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), + UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694), + UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), + UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65), + UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), + UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5), + UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), + UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4), + UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), + UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70), + UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), + UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df), + UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), + UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b), + UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), + UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30), + UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), + UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8), + UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), + UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8), + UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), + UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3), + UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), + UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec), + UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), + UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b), + UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), + UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178), + UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), + UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b), + UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), + UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c), + UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), + UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817) +}; + +/* Various logical functions */ + +#define ROR64c(x, y) \ + ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \ + ((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF)) + +#define STORE64H(x, y) \ + { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255); \ + (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255); \ + (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255); \ + (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); } + +#define LOAD64H(x, y) \ + { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \ + (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \ + (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \ + (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); } + + +#define Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Maj(x,y,z) (((x | y) & z) | (x & y)) +#define S(x, n) ROR64c(x, n) +#define R(x, n) (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n)) +#define Sigma0(x) (S(x, 28) ^ S(x, 34) ^ S(x, 39)) +#define Sigma1(x) (S(x, 14) ^ S(x, 18) ^ S(x, 41)) +#define Gamma0(x) (S(x, 1) ^ S(x, 8) ^ R(x, 7)) +#define Gamma1(x) (S(x, 19) ^ S(x, 61) ^ R(x, 6)) +#ifndef MIN +#define MIN(x, y) ( ((x)<(y))?(x):(y) ) +#endif + +/* compress 1024-bits */ +static int sha512_compress(sha512_context *md, const unsigned char *buf) { + uint64_t S[8], W[80], t0, t1; + int i; + + /* copy state into S */ + for(i = 0; i < 8; i++) { + S[i] = md->state[i]; + } + + /* copy the state into 1024-bits into W[0..15] */ + for(i = 0; i < 16; i++) { + LOAD64H(W[i], buf + (8 * i)); + } + + /* fill W[16..79] */ + for(i = 16; i < 80; i++) { + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + } + + /* Compress */ +#define RND(a,b,c,d,e,f,g,h,i) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c);\ + d += t0; \ + h = t0 + t1; + + for(i = 0; i < 80; i += 8) { + RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i + 0); + RND(S[7], S[0], S[1], S[2], S[3], S[4], S[5], S[6], i + 1); + RND(S[6], S[7], S[0], S[1], S[2], S[3], S[4], S[5], i + 2); + RND(S[5], S[6], S[7], S[0], S[1], S[2], S[3], S[4], i + 3); + RND(S[4], S[5], S[6], S[7], S[0], S[1], S[2], S[3], i + 4); + RND(S[3], S[4], S[5], S[6], S[7], S[0], S[1], S[2], i + 5); + RND(S[2], S[3], S[4], S[5], S[6], S[7], S[0], S[1], i + 6); + RND(S[1], S[2], S[3], S[4], S[5], S[6], S[7], S[0], i + 7); + } + +#undef RND + + + + /* feedback */ + for(i = 0; i < 8; i++) { + md->state[i] = md->state[i] + S[i]; + } + + return 0; +} + + +/** + Initialize the hash state + @param md The hash state you wish to initialize + @return 0 if successful +*/ +int sha512_init(sha512_context *md) { + if(md == NULL) { + return 1; + } + + md->curlen = 0; + md->length = 0; + md->state[0] = UINT64_C(0x6a09e667f3bcc908); + md->state[1] = UINT64_C(0xbb67ae8584caa73b); + md->state[2] = UINT64_C(0x3c6ef372fe94f82b); + md->state[3] = UINT64_C(0xa54ff53a5f1d36f1); + md->state[4] = UINT64_C(0x510e527fade682d1); + md->state[5] = UINT64_C(0x9b05688c2b3e6c1f); + md->state[6] = UINT64_C(0x1f83d9abfb41bd6b); + md->state[7] = UINT64_C(0x5be0cd19137e2179); + + return 0; +} + +/** + Process a block of memory though the hash + @param md The hash state + @param in The data to hash + @param inlen The length of the data (octets) + @return 0 if successful +*/ +int sha512_update(sha512_context *md, const void *vin, size_t inlen) { + const unsigned char *in = vin; + size_t n; + size_t i; + int err; + + if(md == NULL) { + return 1; + } + + if(in == NULL) { + return 1; + } + + if(md->curlen > sizeof(md->buf)) { + return 1; + } + + while(inlen > 0) { + if(md->curlen == 0 && inlen >= 128) { + if((err = sha512_compress(md, in)) != 0) { + return err; + } + + md->length += 128 * 8; + in += 128; + inlen -= 128; + } else { + n = MIN(inlen, (128 - md->curlen)); + + for(i = 0; i < n; i++) { + md->buf[i + md->curlen] = in[i]; + } + + + md->curlen += n; + in += n; + inlen -= n; + + if(md->curlen == 128) { + if((err = sha512_compress(md, md->buf)) != 0) { + return err; + } + + md->length += 8 * 128; + md->curlen = 0; + } + } + } + + return 0; +} + +/** + Terminate the hash to get the digest + @param md The hash state + @param out [out] The destination of the hash (64 bytes) + @return 0 if successful +*/ +int sha512_final(sha512_context *md, void *vout) { + int i; + unsigned char *out = vout; + + if(md == NULL) { + return 1; + } + + if(out == NULL) { + return 1; + } + + if(md->curlen >= sizeof(md->buf)) { + return 1; + } + + /* increase the length of the message */ + md->length += md->curlen * UINT64_C(8); + + /* append the '1' bit */ + md->buf[md->curlen++] = (unsigned char)0x80; + + /* if the length is currently above 112 bytes we append zeros + * then compress. Then we can fall back to padding zeros and length + * encoding like normal. + */ + if(md->curlen > 112) { + while(md->curlen < 128) { + md->buf[md->curlen++] = (unsigned char)0; + } + + sha512_compress(md, md->buf); + md->curlen = 0; + } + + /* pad up to 120 bytes of zeroes + * note: that from 112 to 120 is the 64 MSB of the length. We assume that you won't hash + * > 2^64 bits of data... :-) + */ + while(md->curlen < 120) { + md->buf[md->curlen++] = (unsigned char)0; + } + + /* store length */ + STORE64H(md->length, md->buf + 120); + sha512_compress(md, md->buf); + + /* copy output */ + for(i = 0; i < 8; i++) { + STORE64H(md->state[i], out + (8 * i)); + } + + return 0; +} + +int sha512(const void *message, size_t message_len, void *out) { + sha512_context ctx; + int ret; + + if((ret = sha512_init(&ctx))) { + return ret; + } + + if((ret = sha512_update(&ctx, message, message_len))) { + return ret; + } + + if((ret = sha512_final(&ctx, out))) { + return ret; + } + + return 0; +} diff --git a/src/ed25519/sha512.h b/src/ed25519/sha512.h new file mode 100644 index 0000000..95461ce --- /dev/null +++ b/src/ed25519/sha512.h @@ -0,0 +1,21 @@ +#ifndef SHA512_H +#define SHA512_H + +#include + +#include "fixedint.h" + +/* state */ +typedef struct sha512_context_ { + uint64_t length, state[8]; + size_t curlen; + unsigned char buf[128]; +} sha512_context; + + +int sha512_init(sha512_context *md); +int sha512_final(sha512_context *md, void *out); +int sha512_update(sha512_context *md, const void *in, size_t inlen); +int sha512(const void *message, size_t message_len, void *out); + +#endif diff --git a/src/ed25519/sign.c b/src/ed25519/sign.c new file mode 100644 index 0000000..0c4eea9 --- /dev/null +++ b/src/ed25519/sign.c @@ -0,0 +1,31 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + + +void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) { + sha512_context hash; + unsigned char hram[64]; + unsigned char r[64]; + ge_p3 R; + + + sha512_init(&hash); + sha512_update(&hash, private_key + 32, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, r); + + sc_reduce(r); + ge_scalarmult_base(&R, r); + ge_p3_tobytes(signature, &R); + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, hram); + + sc_reduce(hram); + sc_muladd(signature + 32, hram, private_key, r); +} diff --git a/src/ed25519/verify.c b/src/ed25519/verify.c new file mode 100644 index 0000000..415e94f --- /dev/null +++ b/src/ed25519/verify.c @@ -0,0 +1,77 @@ +#include "ed25519.h" +#include "sha512.h" +#include "ge.h" +#include "sc.h" + +static int consttime_equal(const unsigned char *x, const unsigned char *y) { + unsigned char r = 0; + + r = x[0] ^ y[0]; +#define F(i) r |= x[i] ^ y[i] + F(1); + F(2); + F(3); + F(4); + F(5); + F(6); + F(7); + F(8); + F(9); + F(10); + F(11); + F(12); + F(13); + F(14); + F(15); + F(16); + F(17); + F(18); + F(19); + F(20); + F(21); + F(22); + F(23); + F(24); + F(25); + F(26); + F(27); + F(28); + F(29); + F(30); + F(31); +#undef F + + return !r; +} + +int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) { + unsigned char h[64]; + unsigned char checker[32]; + sha512_context hash; + ge_p3 A; + ge_p2 R; + + if(signature[63] & 224) { + return 0; + } + + if(ge_frombytes_negate_vartime(&A, public_key) != 0) { + return 0; + } + + sha512_init(&hash); + sha512_update(&hash, signature, 32); + sha512_update(&hash, public_key, 32); + sha512_update(&hash, message, message_len); + sha512_final(&hash, h); + + sc_reduce(h); + ge_double_scalarmult_vartime(&R, h, &A, signature + 32); + ge_tobytes(checker, &R); + + if(!consttime_equal(checker, signature)) { + return 0; + } + + return 1; +} diff --git a/src/edge.c b/src/edge.c index c30d6cc..86970ce 100644 --- a/src/edge.c +++ b/src/edge.c @@ -1,6 +1,6 @@ /* edge.c -- edge tree management - Copyright (C) 2000-2006 Guus Sliepen , + Copyright (C) 2000-2021 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -20,7 +20,8 @@ #include "system.h" -#include "avl_tree.h" +#include "splay_tree.h" +#include "control_common.h" #include "edge.h" #include "logger.h" #include "netutl.h" @@ -28,7 +29,7 @@ #include "utils.h" #include "xalloc.h" -avl_tree_t *edge_weight_tree; /* Tree with all edges, sorted on weight */ +splay_tree_t *edge_weight_tree; static int edge_compare(const edge_t *a, const edge_t *b) { return strcmp(a->to->name, b->to->name); @@ -53,42 +54,55 @@ static int edge_weight_compare(const edge_t *a, const edge_t *b) { } void init_edges(void) { - edge_weight_tree = avl_alloc_tree((avl_compare_t) edge_weight_compare, NULL); + edge_weight_tree = splay_alloc_tree((splay_compare_t) edge_weight_compare, NULL); } -avl_tree_t *new_edge_tree(void) { - return avl_alloc_tree((avl_compare_t) edge_compare, (avl_action_t) free_edge); +splay_tree_t *new_edge_tree(void) { + return splay_alloc_tree((splay_compare_t) edge_compare, (splay_action_t) free_edge); } -void free_edge_tree(avl_tree_t *edge_tree) { - avl_delete_tree(edge_tree); +void free_edge_tree(splay_tree_t *edge_tree) { + splay_delete_tree(edge_tree); } void exit_edges(void) { - avl_delete_tree(edge_weight_tree); + splay_delete_tree(edge_weight_tree); } /* Creation and deletion of connection elements */ edge_t *new_edge(void) { - return xmalloc_and_zero(sizeof(edge_t)); + return xzalloc(sizeof(edge_t)); } void free_edge(edge_t *e) { sockaddrfree(&e->address); + sockaddrfree(&e->local_address); free(e); } void edge_add(edge_t *e) { - avl_insert(edge_weight_tree, e); - avl_insert(e->from->edge_tree, e); + splay_node_t *node = splay_insert(e->from->edge_tree, e); + + if(!node) { + logger(DEBUG_ALWAYS, LOG_ERR, "Edge from %s to %s already exists in edge_tree\n", e->from->name, e->to->name); + return; + } + e->reverse = lookup_edge(e->to, e->from); if(e->reverse) { e->reverse->reverse = e; } + + node = splay_insert(edge_weight_tree, e); + + if(!node) { + logger(DEBUG_ALWAYS, LOG_ERR, "Edge from %s to %s already exists in edge_weight_tree\n", e->from->name, e->to->name); + return; + } } void edge_del(edge_t *e) { @@ -96,8 +110,8 @@ void edge_del(edge_t *e) { e->reverse->reverse = NULL; } - avl_delete(edge_weight_tree, e); - avl_delete(e->from->edge_tree, e); + splay_delete(edge_weight_tree, e); + splay_delete(e->from->edge_tree, e); } edge_t *lookup_edge(node_t *from, node_t *to) { @@ -106,28 +120,22 @@ edge_t *lookup_edge(node_t *from, node_t *to) { v.from = from; v.to = to; - return avl_search(from->edge_tree, &v); + return splay_search(from->edge_tree, &v); } -void dump_edges(void) { - avl_node_t *node, *node2; - node_t *n; - edge_t *e; - char *address; - - logger(LOG_DEBUG, "Edges:"); - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - - for(node2 = n->edge_tree->head; node2; node2 = node2->next) { - e = node2->data; - address = sockaddr2hostname(&e->address); - logger(LOG_DEBUG, " %s to %s at %s options %x weight %d", - e->from->name, e->to->name, address, e->options, e->weight); +bool dump_edges(connection_t *c) { + for splay_each(node_t, n, node_tree) { + for splay_each(edge_t, e, n->edge_tree) { + char *address = sockaddr2hostname(&e->address); + char *local_address = sockaddr2hostname(&e->local_address); + send_request(c, "%d %d %s %s %s %s %x %d", + CONTROL, REQ_DUMP_EDGES, + e->from->name, e->to->name, address, + local_address, e->options, e->weight); free(address); + free(local_address); } } - logger(LOG_DEBUG, "End of edges."); + return send_request(c, "%d %d", CONTROL, REQ_DUMP_EDGES); } diff --git a/src/edge.h b/src/edge.h index a7a6302..032acbc 100644 --- a/src/edge.h +++ b/src/edge.h @@ -3,7 +3,7 @@ /* edge.h -- header for edge.c - Copyright (C) 2001-2006 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,7 +21,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "avl_tree.h" +#include "splay_tree.h" #include "connection.h" #include "net.h" #include "node.h" @@ -30,25 +30,26 @@ typedef struct edge_t { struct node_t *from; struct node_t *to; sockaddr_t address; + sockaddr_t local_address; - uint32_t options; /* options turned on for this edge */ - int weight; /* weight of this edge */ + uint32_t options; /* options turned on for this edge */ + int weight; /* weight of this edge */ - struct connection_t *connection; /* connection associated with this edge, if available */ - struct edge_t *reverse; /* edge in the opposite direction, if available */ + struct connection_t *connection; /* connection associated with this edge, if available */ + struct edge_t *reverse; /* edge in the opposite direction, if available */ } edge_t; -extern avl_tree_t *edge_weight_tree; /* Tree with all known edges sorted on weight */ +extern splay_tree_t *edge_weight_tree; /* Tree with all known edges sorted on weight */ extern void init_edges(void); extern void exit_edges(void); extern edge_t *new_edge(void) __attribute__((__malloc__)); extern void free_edge(edge_t *e); -extern avl_tree_t *new_edge_tree(void) __attribute__((__malloc__)); -extern void free_edge_tree(avl_tree_t *edge_tree); +extern splay_tree_t *new_edge_tree(void) __attribute__((__malloc__)); +extern void free_edge_tree(splay_tree_t *edge_tree); extern void edge_add(edge_t *e); extern void edge_del(edge_t *e); extern edge_t *lookup_edge(struct node_t *from, struct node_t *to); -extern void dump_edges(void); +extern bool dump_edges(struct connection_t *c); #endif diff --git a/src/ethernet.h b/src/ethernet.h index 32f5553..086f572 100644 --- a/src/ethernet.h +++ b/src/ethernet.h @@ -25,6 +25,15 @@ #define ETH_ALEN 6 #endif +#ifndef ETH_HLEN +#define ETH_HLEN 14 +#endif + +#ifndef ETHER_TYPE_LEN +#define ETHER_TYPE_LEN 2 +#endif + + #ifndef ARPHRD_ETHER #define ARPHRD_ETHER 1 #endif @@ -45,12 +54,16 @@ #define ETH_P_8021Q 0x8100 #endif +#ifndef ETH_P_MAX +#define ETH_P_MAX 0xFFFF +#endif + #ifndef HAVE_STRUCT_ETHER_HEADER struct ether_header { uint8_t ether_dhost[ETH_ALEN]; uint8_t ether_shost[ETH_ALEN]; uint16_t ether_type; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #endif #ifndef HAVE_STRUCT_ARPHDR @@ -60,7 +73,7 @@ struct arphdr { uint8_t ar_hln; uint8_t ar_pln; uint16_t ar_op; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #define ARPOP_REQUEST 1 #define ARPOP_REPLY 2 @@ -78,7 +91,7 @@ struct ether_arp { uint8_t arp_spa[4]; uint8_t arp_tha[ETH_ALEN]; uint8_t arp_tpa[4]; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #define arp_hrd ea_hdr.ar_hrd #define arp_pro ea_hdr.ar_pro #define arp_hln ea_hdr.ar_hln diff --git a/src/event.c b/src/event.c index 1223222..6603ebf 100644 --- a/src/event.c +++ b/src/event.c @@ -1,7 +1,6 @@ /* - event.c -- event queue - Copyright (C) 2002-2009 Guus Sliepen , - 2002-2005 Ivo Timmermans + event.c -- I/O, timeout and signal event handling + Copyright (C) 2012-2021 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 @@ -20,102 +19,471 @@ #include "system.h" -#include "avl_tree.h" +#include "dropin.h" #include "event.h" +#include "net.h" #include "utils.h" #include "xalloc.h" -avl_tree_t *event_tree; -extern time_t now; +struct timeval now; -static int id; +#ifndef HAVE_MINGW +static fd_set readfds; +static fd_set writefds; +#else +static const long READ_EVENTS = FD_READ | FD_ACCEPT | FD_CLOSE; +static const long WRITE_EVENTS = FD_WRITE | FD_CONNECT; +static DWORD event_count = 0; +#endif +static bool running; -static int event_compare(const event_t *a, const event_t *b) { - if(a->time > b->time) { - return 1; - } +static int io_compare(const io_t *a, const io_t *b) { +#ifndef HAVE_MINGW + return a->fd - b->fd; +#else - if(a->time < b->time) { + if(a->event < b->event) { return -1; } - return a->id - b->id; + if(a->event > b->event) { + return 1; + } + + return 0; +#endif } -void init_events(void) { - event_tree = avl_alloc_tree((avl_compare_t) event_compare, (avl_action_t) free_event); +static int timeout_compare(const timeout_t *a, const timeout_t *b) { + struct timeval diff; + timersub(&a->tv, &b->tv, &diff); + + if(diff.tv_sec < 0) { + return -1; + } + + if(diff.tv_sec > 0) { + return 1; + } + + if(diff.tv_usec < 0) { + return -1; + } + + if(diff.tv_usec > 0) { + return 1; + } + + if(a < b) { + return -1; + } + + if(a > b) { + return 1; + } + + return 0; } -void exit_events(void) { - avl_delete_tree(event_tree); -} +static splay_tree_t io_tree = {.compare = (splay_compare_t)io_compare}; +static splay_tree_t timeout_tree = {.compare = (splay_compare_t)timeout_compare}; -void expire_events(void) { - avl_node_t *node; - event_t *event; - time_t diff; - - /* - * Make all events appear expired by subtracting the difference between - * the expiration time of the last event and the current time. - */ - - if(!event_tree->tail) { +void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags) { + if(io->cb) { return; } - event = event_tree->tail->data; + io->fd = fd; +#ifdef HAVE_MINGW - if(event->time <= now) { - return; - } + if(io->fd != -1) { + io->event = WSACreateEvent(); - diff = event->time - now; - - for(node = event_tree->head; node; node = node->next) { - event = node->data; - event->time -= diff; - } -} - -event_t *new_event(void) { - return xmalloc_and_zero(sizeof(event_t)); -} - -void free_event(event_t *event) { - free(event); -} - -void event_add(event_t *event) { - event->id = ++id; - avl_insert(event_tree, event); -} - -void event_del(event_t *event) { - avl_delete(event_tree, event); -} - -event_t *get_expired_event(void) { - event_t *event; - - if(event_tree->head) { - event = event_tree->head->data; - - if(event->time <= now) { - avl_node_t *node = event_tree->head; - avl_unlink_node(event_tree, node); - free(node); - return event; + if(io->event == WSA_INVALID_EVENT) { + abort(); } } - return NULL; + event_count++; +#endif + io->cb = cb; + io->data = data; + io->node.data = io; + + io_set(io, flags); + + if(!splay_insert_node(&io_tree, &io->node)) { + abort(); + } } -event_t *peek_next_event(void) { - if(event_tree->head) { - return event_tree->head->data; +#ifdef HAVE_MINGW +void io_add_event(io_t *io, io_cb_t cb, void *data, WSAEVENT event) { + io->event = event; + io_add(io, cb, data, -1, 0); +} +#endif + +void io_set(io_t *io, int flags) { + if(flags == io->flags) { + return; } - return NULL; + io->flags = flags; + + if(io->fd == -1) { + return; + } + +#ifndef HAVE_MINGW + + if(flags & IO_READ) { + FD_SET(io->fd, &readfds); + } else { + FD_CLR(io->fd, &readfds); + } + + if(flags & IO_WRITE) { + FD_SET(io->fd, &writefds); + } else { + FD_CLR(io->fd, &writefds); + } + +#else + long events = 0; + + if(flags & IO_WRITE) { + events |= WRITE_EVENTS; + } + + if(flags & IO_READ) { + events |= READ_EVENTS; + } + + if(WSAEventSelect(io->fd, io->event, events) != 0) { + abort(); + } + +#endif +} + +void io_del(io_t *io) { + if(!io->cb) { + return; + } + + io_set(io, 0); +#ifdef HAVE_MINGW + + if(io->fd != -1 && WSACloseEvent(io->event) == FALSE) { + abort(); + } + + event_count--; +#endif + + splay_unlink_node(&io_tree, &io->node); + io->cb = NULL; +} + +void timeout_add(timeout_t *timeout, timeout_cb_t cb, void *data, struct timeval *tv) { + timeout->cb = cb; + timeout->data = data; + timeout->node.data = timeout; + + timeout_set(timeout, tv); +} + +void timeout_set(timeout_t *timeout, struct timeval *tv) { + if(timerisset(&timeout->tv)) { + splay_unlink_node(&timeout_tree, &timeout->node); + } + + if(!now.tv_sec) { + gettimeofday(&now, NULL); + } + + timeradd(&now, tv, &timeout->tv); + + if(!splay_insert_node(&timeout_tree, &timeout->node)) { + abort(); + } +} + +void timeout_del(timeout_t *timeout) { + if(!timeout->cb) { + return; + } + + splay_unlink_node(&timeout_tree, &timeout->node); + timeout->cb = 0; + timeout->tv = (struct timeval) { + 0, 0 + }; +} + +#ifndef HAVE_MINGW +static int signal_compare(const signal_t *a, const signal_t *b) { + return a->signum - b->signum; +} + +static io_t signalio; +static int pipefd[2] = {-1, -1}; +static splay_tree_t signal_tree = {.compare = (splay_compare_t)signal_compare}; + +static void signal_handler(int signum) { + unsigned char num = signum; + write(pipefd[1], &num, 1); +} + +static void signalio_handler(void *data, int flags) { + (void)data; + (void)flags; + unsigned char signum; + + if(read(pipefd[0], &signum, 1) != 1) { + return; + } + + signal_t *sig = splay_search(&signal_tree, &((signal_t) { + .signum = signum + })); + + if(sig) { + sig->cb(sig->data); + } +} + +static void pipe_init(void) { + if(!pipe(pipefd)) { + io_add(&signalio, signalio_handler, NULL, pipefd[0], IO_READ); + } +} + +void signal_add(signal_t *sig, signal_cb_t cb, void *data, int signum) { + if(sig->cb) { + return; + } + + sig->cb = cb; + sig->data = data; + sig->signum = signum; + sig->node.data = sig; + + if(pipefd[0] == -1) { + pipe_init(); + } + + signal(sig->signum, signal_handler); + + if(!splay_insert_node(&signal_tree, &sig->node)) { + abort(); + } +} + +void signal_del(signal_t *sig) { + if(!sig->cb) { + return; + } + + signal(sig->signum, SIG_DFL); + + splay_unlink_node(&signal_tree, &sig->node); + sig->cb = NULL; +} +#endif + +static struct timeval *get_time_remaining(struct timeval *diff) { + gettimeofday(&now, NULL); + struct timeval *tv = NULL; + + while(timeout_tree.head) { + timeout_t *timeout = timeout_tree.head->data; + timersub(&timeout->tv, &now, diff); + + if(diff->tv_sec < 0) { + timeout->cb(timeout->data); + + if(timercmp(&timeout->tv, &now, <)) { + timeout_del(timeout); + } + } else { + tv = diff; + break; + } + } + + return tv; +} + +bool event_loop(void) { + running = true; + +#ifndef HAVE_MINGW + fd_set readable; + fd_set writable; + + while(running) { + struct timeval diff; + struct timeval *tv = get_time_remaining(&diff); + memcpy(&readable, &readfds, sizeof(readable)); + memcpy(&writable, &writefds, sizeof(writable)); + + int fds = 0; + + if(io_tree.tail) { + io_t *last = io_tree.tail->data; + fds = last->fd + 1; + } + + int n = select(fds, &readable, &writable, NULL, tv); + + if(n < 0) { + if(sockwouldblock(sockerrno)) { + continue; + } else { + return false; + } + } + + if(!n) { + continue; + } + + unsigned int curgen = io_tree.generation; + + for splay_each(io_t, io, &io_tree) { + if(FD_ISSET(io->fd, &writable)) { + io->cb(io->data, IO_WRITE); + } else if(FD_ISSET(io->fd, &readable)) { + io->cb(io->data, IO_READ); + } else { + continue; + } + + /* + There are scenarios in which the callback will remove another io_t from the tree + (e.g. closing a double connection). Since splay_each does not support that, we + need to exit the loop if that happens. That's okay, since any remaining events will + get picked up by the next select() call. + */ + if(curgen != io_tree.generation) { + break; + } + } + } + +#else + + while(running) { + struct timeval diff; + struct timeval *tv = get_time_remaining(&diff); + DWORD timeout_ms = tv ? (DWORD)(tv->tv_sec * 1000 + tv->tv_usec / 1000 + 1) : WSA_INFINITE; + + if(!event_count) { + Sleep(timeout_ms); + continue; + } + + /* + For some reason, Microsoft decided to make the FD_WRITE event edge-triggered instead of level-triggered, + which is the opposite of what select() does. In practice, that means that if a FD_WRITE event triggers, + it will never trigger again until a send() returns EWOULDBLOCK. Since the semantics of this event loop + is that write events are level-triggered (i.e. they continue firing until the socket is full), we need + to emulate these semantics by making sure we fire each IO_WRITE that is still writeable. + + Note that technically FD_CLOSE has the same problem, but it's okay because user code does not rely on + this event being fired again if ignored. + */ + unsigned int curgen = io_tree.generation; + + for splay_each(io_t, io, &io_tree) { + if(io->flags & IO_WRITE && send(io->fd, NULL, 0, 0) == 0) { + io->cb(io->data, IO_WRITE); + + if(curgen != io_tree.generation) { + break; + } + } + } + + if(event_count > WSA_MAXIMUM_WAIT_EVENTS) { + WSASetLastError(WSA_INVALID_PARAMETER); + return(false); + } + + WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS]; + io_t *io_map[WSA_MAXIMUM_WAIT_EVENTS]; + DWORD event_index = 0; + + for splay_each(io_t, io, &io_tree) { + events[event_index] = io->event; + io_map[event_index] = io; + event_index++; + } + + /* + * If the generation number changes due to event addition + * or removal by a callback we restart the loop. + */ + curgen = io_tree.generation; + + for(DWORD event_offset = 0; event_offset < event_count;) { + DWORD result = WSAWaitForMultipleEvents(event_count - event_offset, &events[event_offset], FALSE, timeout_ms, FALSE); + + if(result == WSA_WAIT_TIMEOUT) { + break; + } + + if(result < WSA_WAIT_EVENT_0 || result >= WSA_WAIT_EVENT_0 + event_count - event_offset) { + return false; + } + + /* Look up io in the map by index. */ + event_index = result - WSA_WAIT_EVENT_0 + event_offset; + io_t *io = io_map[event_index]; + + if(io->fd == -1) { + io->cb(io->data, 0); + + if(curgen != io_tree.generation) { + break; + } + } else { + WSANETWORKEVENTS network_events; + + if(WSAEnumNetworkEvents(io->fd, io->event, &network_events) != 0) { + return(false); + } + + if(network_events.lNetworkEvents & READ_EVENTS) { + io->cb(io->data, IO_READ); + + if(curgen != io_tree.generation) { + break; + } + } + + /* + The fd might be available for write too. However, if we already fired the read callback, that + callback might have deleted the io (e.g. through terminate_connection()), so we can't fire the + write callback here. Instead, we loop back and let the writable io loop above handle it. + */ + } + + /* Continue checking the rest of the events. */ + event_offset = event_index + 1; + + /* Just poll the next time through. */ + timeout_ms = 0; + } + } + +#endif + + return true; +} + +void event_exit(void) { + running = false; } diff --git a/src/event.h b/src/event.h index 6d521bb..7adaf85 100644 --- a/src/event.h +++ b/src/event.h @@ -2,9 +2,8 @@ #define TINC_EVENT_H /* - event.h -- header for event.c - Copyright (C) 2002-2009 Guus Sliepen , - 2002-2005 Ivo Timmermans + event.h -- I/O, timeout and signal event handling + Copyright (C) 2012-2013 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 @@ -21,27 +20,57 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "avl_tree.h" +#include "splay_tree.h" -extern avl_tree_t *event_tree; +#define IO_READ 1 +#define IO_WRITE 2 -typedef void (*event_handler_t)(void *); +typedef void (*io_cb_t)(void *data, int flags); +typedef void (*timeout_cb_t)(void *data); +typedef void (*signal_cb_t)(void *data); -typedef struct event { - time_t time; - int id; - event_handler_t handler; +typedef struct io_t { + int fd; + int flags; +#ifdef HAVE_MINGW + WSAEVENT event; +#endif + io_cb_t cb; void *data; -} event_t; + splay_node_t node; +} io_t; -extern void init_events(void); -extern void exit_events(void); -extern void expire_events(void); -extern event_t *new_event(void) __attribute__((__malloc__)); -extern void free_event(event_t *event); -extern void event_add(event_t *event); -extern void event_del(event_t *event); -extern event_t *get_expired_event(void); -extern event_t *peek_next_event(void); +typedef struct timeout_t { + struct timeval tv; + timeout_cb_t cb; + void *data; + splay_node_t node; +} timeout_t; + +typedef struct signal_t { + int signum; + signal_cb_t cb; + void *data; + splay_node_t node; +} signal_t; + +extern struct timeval now; + +extern void io_add(io_t *io, io_cb_t cb, void *data, int fd, int flags); +#ifdef HAVE_MINGW +extern void io_add_event(io_t *io, io_cb_t cb, void *data, WSAEVENT event); +#endif +extern void io_del(io_t *io); +extern void io_set(io_t *io, int flags); + +extern void timeout_add(timeout_t *timeout, timeout_cb_t cb, void *data, struct timeval *tv); +extern void timeout_del(timeout_t *timeout); +extern void timeout_set(timeout_t *timeout, struct timeval *tv); + +extern void signal_add(signal_t *sig, signal_cb_t cb, void *data, int signum); +extern void signal_del(signal_t *sig); + +extern bool event_loop(void); +extern void event_exit(void); #endif diff --git a/src/fake-getaddrinfo.c b/src/fake-getaddrinfo.c deleted file mode 100644 index 1ee11c5..0000000 --- a/src/fake-getaddrinfo.c +++ /dev/null @@ -1,108 +0,0 @@ -/* - * fake library for ssh - * - * This file includes getaddrinfo(), freeaddrinfo() and gai_strerror(). - * These functions are defined in rfc2133. - * - * But these functions are not implemented correctly. The minimum subset - * is implemented for ssh use only. For example, this routine assumes - * that ai_family is AF_INET. Don't use it for another purpose. - */ - -#include "system.h" - -#include "ipv4.h" -#include "ipv6.h" -#include "fake-getaddrinfo.h" -#include "xalloc.h" - -#if !HAVE_DECL_GAI_STRERROR -char *gai_strerror(int ecode) { - switch(ecode) { - case EAI_NODATA: - return "No address associated with hostname"; - - case EAI_MEMORY: - return "Memory allocation failure"; - - case EAI_FAMILY: - return "Address family not supported"; - - default: - return "Unknown error"; - } -} -#endif /* !HAVE_GAI_STRERROR */ - -#if !HAVE_DECL_FREEADDRINFO -void freeaddrinfo(struct addrinfo *ai) { - struct addrinfo *next; - - while(ai) { - next = ai->ai_next; - free(ai); - ai = next; - } -} -#endif /* !HAVE_FREEADDRINFO */ - -#if !HAVE_DECL_GETADDRINFO -static struct addrinfo *malloc_ai(uint16_t port, uint32_t addr) { - struct addrinfo *ai; - - ai = xmalloc_and_zero(sizeof(struct addrinfo) + sizeof(struct sockaddr_in)); - - ai->ai_addr = (struct sockaddr *)(ai + 1); - ai->ai_addrlen = sizeof(struct sockaddr_in); - ai->ai_addr->sa_family = ai->ai_family = AF_INET; - - ((struct sockaddr_in *)(ai)->ai_addr)->sin_port = port; - ((struct sockaddr_in *)(ai)->ai_addr)->sin_addr.s_addr = addr; - - return ai; -} - -int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res) { - struct addrinfo *prev = NULL; - struct hostent *hp; - struct in_addr in = {0}; - int i; - uint16_t port = 0; - - if(hints && hints->ai_family != AF_INET && hints->ai_family != AF_UNSPEC) { - return EAI_FAMILY; - } - - if(servname) { - port = htons(atoi(servname)); - } - - if(hints && hints->ai_flags & AI_PASSIVE) { - *res = malloc_ai(port, htonl(0x00000000)); - return 0; - } - - if(!hostname) { - *res = malloc_ai(port, htonl(0x7f000001)); - return 0; - } - - hp = gethostbyname(hostname); - - if(!hp || !hp->h_addr_list || !hp->h_addr_list[0]) { - return EAI_NODATA; - } - - for(i = 0; hp->h_addr_list[i]; i++) { - *res = malloc_ai(port, ((struct in_addr *)hp->h_addr_list[i])->s_addr); - - if(prev) { - prev->ai_next = *res; - } - - prev = *res; - } - - return 0; -} -#endif /* !HAVE_GETADDRINFO */ diff --git a/src/fake-getaddrinfo.h b/src/fake-getaddrinfo.h deleted file mode 100644 index f10cb83..0000000 --- a/src/fake-getaddrinfo.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef TINC_FAKE_GETADDRINFO_H -#define TINC_FAKE_GETADDRINFO_H - -#ifndef EAI_NODATA -#define EAI_NODATA 1 -#endif - -#ifndef EAI_MEMORY -#define EAI_MEMORY 2 -#endif - -#ifndef EAI_FAMILY -#define EAI_FAMILY 3 -#endif - -#ifndef AI_PASSIVE -# define AI_PASSIVE 1 -# define AI_CANONNAME 2 -#endif - -#ifndef NI_NUMERICHOST -# define NI_NUMERICHOST 2 -# define NI_NAMEREQD 4 -# define NI_NUMERICSERV 8 -#endif - -#ifndef AI_NUMERICHOST -#define AI_NUMERICHOST 4 -#endif - -#ifndef HAVE_STRUCT_ADDRINFO -struct addrinfo { - int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ - int ai_family; /* PF_xxx */ - int ai_socktype; /* SOCK_xxx */ - int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ - size_t ai_addrlen; /* length of ai_addr */ - char *ai_canonname; /* canonical name for hostname */ - struct sockaddr *ai_addr; /* binary address */ - struct addrinfo *ai_next; /* next structure in linked list */ -}; -#endif /* !HAVE_STRUCT_ADDRINFO */ - -#if !HAVE_DECL_GETADDRINFO -int getaddrinfo(const char *hostname, const char *servname, - const struct addrinfo *hints, struct addrinfo **res); -#endif /* !HAVE_GETADDRINFO */ - -#if !HAVE_DECL_GAI_STRERROR -char *gai_strerror(int ecode); -#endif /* !HAVE_GAI_STRERROR */ - -#if !HAVE_DECL_FREEADDRINFO -void freeaddrinfo(struct addrinfo *ai); -#endif /* !HAVE_FREEADDRINFO */ - -#endif diff --git a/src/fake-getnameinfo.c b/src/fake-getnameinfo.c deleted file mode 100644 index e51bce2..0000000 --- a/src/fake-getnameinfo.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * fake library for ssh - * - * This file includes getnameinfo(). - * These functions are defined in rfc2133. - * - * But these functions are not implemented correctly. The minimum subset - * is implemented for ssh use only. For example, this routine assumes - * that ai_family is AF_INET. Don't use it for another purpose. - */ - -#include "system.h" - -#include "fake-getnameinfo.h" -#include "fake-getaddrinfo.h" - -#if !HAVE_DECL_GETNAMEINFO - -int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags) { - struct sockaddr_in *sin = (struct sockaddr_in *)sa; - struct hostent *hp; - int len; - - if(sa->sa_family != AF_INET) { - return EAI_FAMILY; - } - - if(serv && servlen) { - len = snprintf(serv, servlen, "%d", ntohs(sin->sin_port)); - - if(len < 0 || len >= servlen) { - return EAI_MEMORY; - } - } - - if(!host || !hostlen) { - return 0; - } - - if(flags & NI_NUMERICHOST) { - len = snprintf(host, hostlen, "%s", inet_ntoa(sin->sin_addr)); - - if(len < 0 || len >= hostlen) { - return EAI_MEMORY; - } - - return 0; - } - - hp = gethostbyaddr((char *)&sin->sin_addr, sizeof(struct in_addr), AF_INET); - - if(!hp || !hp->h_name || !hp->h_name[0]) { - return EAI_NODATA; - } - - len = snprintf(host, hostlen, "%s", hp->h_name); - - if(len < 0 || len >= hostlen) { - return EAI_MEMORY; - } - - return 0; -} -#endif /* !HAVE_GETNAMEINFO */ diff --git a/src/fake-getnameinfo.h b/src/fake-getnameinfo.h deleted file mode 100644 index 4f24ad1..0000000 --- a/src/fake-getnameinfo.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TINC_FAKE_GETNAMEINFO_H -#define TINC_FAKE_GETNAMEINFO_H - -#if !HAVE_DECL_GETNAMEINFO -int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, - size_t hostlen, char *serv, size_t servlen, int flags); -#endif /* !HAVE_GETNAMEINFO */ - -#ifndef NI_MAXSERV -# define NI_MAXSERV 32 -#endif /* !NI_MAXSERV */ -#ifndef NI_MAXHOST -# define NI_MAXHOST 1025 -#endif /* !NI_MAXHOST */ - -#endif diff --git a/src/fd_device.c b/src/fd_device.c new file mode 100644 index 0000000..0da8259 --- /dev/null +++ b/src/fd_device.c @@ -0,0 +1,239 @@ +/* + fd_device.c -- Interaction with Android tun fd + Copyright (C) 2001-2005 Ivo Timmermans, + 2001-2021 Guus Sliepen + 2009 Grzegorz Dymarek + 2016-2020 Pacien TRAN-GIRARD + + 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" + +#ifdef HAVE_SYS_UN_H +#include + +#include "conf.h" +#include "device.h" +#include "ethernet.h" +#include "logger.h" +#include "net.h" +#include "route.h" +#include "utils.h" + +struct unix_socket_addr { + size_t size; + struct sockaddr_un addr; +}; + +static int read_fd(int socket) { + char iobuf; + struct iovec iov = {0}; + char cmsgbuf[CMSG_SPACE(sizeof(device_fd))]; + struct msghdr msg = {0}; + int ret; + struct cmsghdr *cmsgptr; + + iov.iov_base = &iobuf; + iov.iov_len = 1; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + if((ret = recvmsg(socket, &msg, 0)) < 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not read from unix socket (error %d)!", ret); + return -1; + } + +#ifdef IP_RECVERR + + if(msg.msg_flags & (MSG_CTRUNC | MSG_OOB | MSG_ERRQUEUE)) { +#else + + if(msg.msg_flags & (MSG_CTRUNC | MSG_OOB)) { +#endif + logger(DEBUG_ALWAYS, LOG_ERR, "Error while receiving message (flags %d)!", msg.msg_flags); + return -1; + } + + cmsgptr = CMSG_FIRSTHDR(&msg); + + if(cmsgptr->cmsg_level != SOL_SOCKET) { + logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG level: %d, expected %d!", + cmsgptr->cmsg_level, SOL_SOCKET); + return -1; + } + + if(cmsgptr->cmsg_type != SCM_RIGHTS) { + logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG type: %d, expected %d!", + cmsgptr->cmsg_type, SCM_RIGHTS); + return -1; + } + + if(cmsgptr->cmsg_len != CMSG_LEN(sizeof(device_fd))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Wrong CMSG data length: %lu, expected %lu!", + (unsigned long)cmsgptr->cmsg_len, (unsigned long)CMSG_LEN(sizeof(device_fd))); + return -1; + } + + return *(int *) CMSG_DATA(cmsgptr); +} + +static int receive_fd(struct unix_socket_addr socket_addr) { + int socketfd; + int ret; + int result; + + if((socketfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open stream socket (error %d)!", socketfd); + return -1; + } + + if((ret = connect(socketfd, (struct sockaddr *) &socket_addr.addr, socket_addr.size)) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not connect to Unix socket (error %d)!", ret); + result = -1; + goto end; + } + + result = read_fd(socketfd); + +end: + close(socketfd); + return result; +} + +static struct unix_socket_addr parse_socket_addr(const char *path) { + struct sockaddr_un socket_addr; + size_t path_length; + + if(strlen(path) >= sizeof(socket_addr.sun_path)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unix socket path too long!"); + return (struct unix_socket_addr) { + 0 + }; + } + + socket_addr.sun_family = AF_UNIX; + strncpy(socket_addr.sun_path, path, sizeof(socket_addr.sun_path)); + + if(path[0] == '@') { + /* abstract namespace socket */ + socket_addr.sun_path[0] = '\0'; + path_length = strlen(path); + } else { + /* filesystem path with NUL terminator */ + path_length = strlen(path) + 1; + } + + return (struct unix_socket_addr) { + .size = offsetof(struct sockaddr_un, sun_path) + path_length, + .addr = socket_addr + }; +} + +static bool setup_device(void) { + if(routing_mode == RMODE_SWITCH) { + logger(DEBUG_ALWAYS, LOG_ERR, "Switch mode not supported (requires unsupported TAP device)!"); + return false; + } + + if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not read device from configuration!"); + return false; + } + + /* device is either directly a file descriptor or an unix socket to read it from */ + if(sscanf(device, "%d", &device_fd) != 1) { + logger(DEBUG_ALWAYS, LOG_INFO, "Receiving fd from Unix socket at %s.", device); + device_fd = receive_fd(parse_socket_addr(device)); + } + + if(device_fd < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s!", device, strerror(errno)); + return false; + } + + logger(DEBUG_ALWAYS, LOG_INFO, "fd/%d adapter set up.", device_fd); + + return true; +} + +static void close_device(void) { + close(device_fd); + device_fd = -1; +} + +static inline uint16_t get_ip_ethertype(vpn_packet_t *packet) { + switch(DATA(packet)[ETH_HLEN] >> 4) { + case 4: + return ETH_P_IP; + + case 6: + return ETH_P_IPV6; + + default: + return ETH_P_MAX; + } +} + +static inline void set_etherheader(vpn_packet_t *packet, uint16_t ethertype) { + memset(DATA(packet), 0, ETH_HLEN - ETHER_TYPE_LEN); + + DATA(packet)[ETH_HLEN - ETHER_TYPE_LEN] = (ethertype >> 8) & 0xFF; + DATA(packet)[ETH_HLEN - ETHER_TYPE_LEN + 1] = ethertype & 0xFF; +} + +static bool read_packet(vpn_packet_t *packet) { + int lenin = read(device_fd, DATA(packet) + ETH_HLEN, MTU - ETH_HLEN); + + if(lenin <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from fd/%d: %s!", device_fd, strerror(errno)); + return false; + } + + uint16_t ethertype = get_ip_ethertype(packet); + + if(ethertype == ETH_P_MAX) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown IP version while reading packet from fd/%d!", device_fd); + return false; + } + + set_etherheader(packet, ethertype); + packet->len = lenin + ETH_HLEN; + + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from fd/%d.", packet->len, device_fd); + + return true; +} + +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to fd/%d.", packet->len, device_fd); + + if(write(device_fd, DATA(packet) + ETH_HLEN, packet->len - ETH_HLEN) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to fd/%d: %s!", device_fd, strerror(errno)); + return false; + } + + return true; +} + +const devops_t fd_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, +}; +#endif diff --git a/src/fsck.c b/src/fsck.c new file mode 100644 index 0000000..a60dcf3 --- /dev/null +++ b/src/fsck.c @@ -0,0 +1,615 @@ +/* + fsck.c -- Check the configuration files for problems + Copyright (C) 2014-2021 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 "crypto.h" +#include "ecdsa.h" +#include "ecdsagen.h" +#include "fsck.h" +#include "names.h" +#ifndef DISABLE_LEGACY +#include "rsa.h" +#include "rsagen.h" +#endif +#include "tincctl.h" +#include "utils.h" + +static bool ask_fix(void) { + if(force) { + return true; + } + + if(!tty) { + return false; + } + +again: + fprintf(stderr, "Fix y/n? "); + char buf[1024]; + + if(!fgets(buf, sizeof(buf), stdin)) { + tty = false; + return false; + } + + if(buf[0] == 'y' || buf[0] == 'Y') { + return true; + } + + if(buf[0] == 'n' || buf[0] == 'N') { + return false; + } + + goto again; +} + +static void print_tinc_cmd(const char *argv0, const char *format, ...) { + if(confbasegiven) { + fprintf(stderr, "%s -c %s ", argv0, confbase); + } else if(netname) { + fprintf(stderr, "%s -n %s ", argv0, netname); + } else { + fprintf(stderr, "%s ", argv0); + } + + va_list va; + va_start(va, format); + vfprintf(stderr, format, va); + va_end(va); + fputc('\n', stderr); +} + +static int strtailcmp(const char *str, const char *tail) { + size_t slen = strlen(str); + size_t tlen = strlen(tail); + + if(tlen > slen) { + return -1; + } + + return memcmp(str + slen - tlen, tail, tlen); +} + +static void check_conffile(const char *fname, bool server) { + (void)server; + + FILE *f = fopen(fname, "r"); + + if(!f) { + fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno)); + return; + } + + char line[2048]; + int lineno = 0; + bool skip = false; + const int maxvariables = 50; + int count[maxvariables]; + memset(count, 0, sizeof(count)); + + while(fgets(line, sizeof(line), f)) { + if(skip) { + if(!strncmp(line, "-----END", 8)) { + skip = false; + } + + continue; + } else { + if(!strncmp(line, "-----BEGIN", 10)) { + skip = true; + continue; + } + } + + int len; + char *variable, *value, *eol; + variable = value = line; + + lineno++; + + eol = line + strlen(line); + + while(strchr("\t \r\n", *--eol)) { + *eol = '\0'; + } + + if(!line[0] || line[0] == '#') { + continue; + } + + len = strcspn(value, "\t ="); + value += len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + variable[len] = '\0'; + + bool found = false; + + for(int i = 0; variables[i].name; i++) { + if(strcasecmp(variables[i].name, variable)) { + continue; + } + + found = true; + + if(variables[i].type & VAR_OBSOLETE) { + fprintf(stderr, "WARNING: obsolete variable %s in %s line %d\n", variable, fname, lineno); + } + + if(i < maxvariables) { + count[i]++; + } + } + + if(!found) { + fprintf(stderr, "WARNING: unknown variable %s in %s line %d\n", variable, fname, lineno); + } + + if(!*value) { + fprintf(stderr, "ERROR: no value for variable %s in %s line %d\n", variable, fname, lineno); + } + } + + for(int i = 0; variables[i].name && i < maxvariables; i++) { + if(count[i] > 1 && !(variables[i].type & VAR_MULTIPLE)) { + fprintf(stderr, "WARNING: multiple instances of variable %s in %s\n", variables[i].name, fname); + } + } + + if(ferror(f)) { + fprintf(stderr, "ERROR: while reading %s: %s\n", fname, strerror(errno)); + } + + fclose(f); +} + +int fsck(const char *argv0) { +#ifdef HAVE_MINGW + int uid = 0; +#else + uid_t uid = getuid(); +#endif + + // Check that tinc.conf is readable. + + if(access(tinc_conf, R_OK)) { + fprintf(stderr, "ERROR: cannot read %s: %s\n", tinc_conf, strerror(errno)); + + if(errno == ENOENT) { + fprintf(stderr, "No tinc configuration found. Create a new one with:\n\n"); + print_tinc_cmd(argv0, "init"); + } else if(errno == EACCES) { + if(uid != 0) { + fprintf(stderr, "You are currently not running tinc as root. Use sudo?\n"); + } else { + fprintf(stderr, "Check the permissions of each component of the path %s.\n", tinc_conf); + } + } + + return 1; + } + + char *name = get_my_name(true); + + if(!name) { + fprintf(stderr, "ERROR: tinc cannot run without a valid Name.\n"); + return 1; + } + + // Check for private keys. + // TODO: use RSAPrivateKeyFile and Ed25519PrivateKeyFile variables if present. + + struct stat st; + char fname[PATH_MAX]; + char dname[PATH_MAX]; + +#ifndef DISABLE_LEGACY + rsa_t *rsa_priv = NULL; + snprintf(fname, sizeof(fname), "%s/rsa_key.priv", confbase); + + if(stat(fname, &st)) { + if(errno != ENOENT) { + // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file. + fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno)); + fprintf(stderr, "Please correct this error.\n"); + return 1; + } + } else { + FILE *f = fopen(fname, "r"); + + if(!f) { + fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno)); + return 1; + } + + rsa_priv = rsa_read_pem_private_key(f); + fclose(f); + + if(!rsa_priv) { + fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname); + fprintf(stderr, "You can generate a new RSA key with:\n\n"); + print_tinc_cmd(argv0, "generate-rsa-keys"); + return 1; + } + +#ifndef HAVE_MINGW + + if(st.st_mode & 077) { + fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname); + + if(st.st_uid != uid) { + fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname); + } else if(ask_fix()) { + if(chmod(fname, st.st_mode & ~077)) { + fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno)); + } else { + fprintf(stderr, "Fixed permissions of %s.\n", fname); + } + } + } + +#endif + } + +#endif + + ecdsa_t *ecdsa_priv = NULL; + snprintf(fname, sizeof(fname), "%s/ed25519_key.priv", confbase); + + if(stat(fname, &st)) { + if(errno != ENOENT) { + // Something is seriously wrong here. If we can access the directory with tinc.conf in it, we should certainly be able to stat() an existing file. + fprintf(stderr, "ERROR: cannot read %s: %s\n", fname, strerror(errno)); + fprintf(stderr, "Please correct this error.\n"); + return 1; + } + } else { + FILE *f = fopen(fname, "r"); + + if(!f) { + fprintf(stderr, "ERROR: could not open %s: %s\n", fname, strerror(errno)); + return 1; + } + + ecdsa_priv = ecdsa_read_pem_private_key(f); + fclose(f); + + if(!ecdsa_priv) { + fprintf(stderr, "ERROR: No key or unusable key found in %s.\n", fname); + fprintf(stderr, "You can generate a new Ed25519 key with:\n\n"); + print_tinc_cmd(argv0, "generate-ed25519-keys"); + return 1; + } + +#ifndef HAVE_MINGW + + if(st.st_mode & 077) { + fprintf(stderr, "WARNING: unsafe file permissions on %s.\n", fname); + + if(st.st_uid != uid) { + fprintf(stderr, "You are not running %s as the same uid as %s.\n", argv0, fname); + } else if(ask_fix()) { + if(chmod(fname, st.st_mode & ~077)) { + fprintf(stderr, "ERROR: could not change permissions of %s: %s\n", fname, strerror(errno)); + } else { + fprintf(stderr, "Fixed permissions of %s.\n", fname); + } + } + } + +#endif + } + +#ifdef DISABLE_LEGACY + + if(!ecdsa_priv) { + fprintf(stderr, "ERROR: No Ed25519 private key found.\n"); +#else + + if(!rsa_priv && !ecdsa_priv) { + fprintf(stderr, "ERROR: Neither RSA or Ed25519 private key found.\n"); +#endif + fprintf(stderr, "You can generate new keys with:\n\n"); + print_tinc_cmd(argv0, "generate-keys"); + return 1; + } + + // Check for public keys. + // TODO: use RSAPublicKeyFile variable if present. + + snprintf(fname, sizeof(fname), "%s/hosts/%s", confbase, name); + + if(access(fname, R_OK)) { + fprintf(stderr, "WARNING: cannot read %s\n", fname); + } + + FILE *f; + +#ifndef DISABLE_LEGACY + rsa_t *rsa_pub = NULL; + + f = fopen(fname, "r"); + + if(f) { + rsa_pub = rsa_read_pem_public_key(f); + fclose(f); + } + + if(rsa_priv) { + if(!rsa_pub) { + fprintf(stderr, "WARNING: No (usable) public RSA key found.\n"); + + if(ask_fix()) { + FILE *f = fopen(fname, "a"); + + if(f) { + if(rsa_write_pem_public_key(rsa_priv, f)) { + fprintf(stderr, "Wrote RSA public key to %s.\n", fname); + } else { + fprintf(stderr, "ERROR: could not write RSA public key to %s.\n", fname); + } + + fclose(f); + } else { + fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno)); + } + } + } else { + // TODO: suggest remedies + size_t len = rsa_size(rsa_priv); + + if(len != rsa_size(rsa_pub)) { + fprintf(stderr, "ERROR: public and private RSA keys do not match.\n"); + return 1; + } + + char *buf1 = malloc(len); + char *buf2 = malloc(len); + char *buf3 = malloc(len); + + randomize(buf1, len); + buf1[0] &= 0x7f; + memset(buf2, 0, len); + memset(buf3, 0, len); + bool result = false; + + if(rsa_public_encrypt(rsa_pub, buf1, len, buf2)) { + if(rsa_private_decrypt(rsa_priv, buf2, len, buf3)) { + if(memcmp(buf1, buf3, len)) { + result = true; + } else { + fprintf(stderr, "ERROR: public and private RSA keys do not match.\n"); + } + } else { + fprintf(stderr, "ERROR: private RSA key does not work.\n"); + } + } else { + fprintf(stderr, "ERROR: public RSA key does not work.\n"); + } + + free(buf3); + free(buf2); + free(buf1); + + if(!result) { + return 1; + } + + } + } else { + if(rsa_pub) { + fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n"); + } + } + +#endif + + ecdsa_t *ecdsa_pub = NULL; + + f = fopen(fname, "r"); + + if(f) { + ecdsa_pub = get_pubkey(f); + + if(!ecdsa_pub) { + rewind(f); + ecdsa_pub = ecdsa_read_pem_public_key(f); + } + + fclose(f); + } + + if(ecdsa_priv) { + if(!ecdsa_pub) { + fprintf(stderr, "WARNING: No (usable) public Ed25519 key found.\n"); + + if(ask_fix()) { + FILE *f = fopen(fname, "a"); + + if(f) { + if(ecdsa_write_pem_public_key(ecdsa_priv, f)) { + fprintf(stderr, "Wrote Ed25519 public key to %s.\n", fname); + } else { + fprintf(stderr, "ERROR: could not write Ed25519 public key to %s.\n", fname); + } + + fclose(f); + } else { + fprintf(stderr, "ERROR: could not append to %s: %s\n", fname, strerror(errno)); + } + } + } else { + // TODO: suggest remedies + char *key1 = ecdsa_get_base64_public_key(ecdsa_pub); + + if(!key1) { + fprintf(stderr, "ERROR: public Ed25519 key does not work.\n"); + return 1; + } + + char *key2 = ecdsa_get_base64_public_key(ecdsa_priv); + + if(!key2) { + free(key1); + fprintf(stderr, "ERROR: private Ed25519 key does not work.\n"); + return 1; + } + + int result = strcmp(key1, key2); + free(key1); + free(key2); + + if(result) { + fprintf(stderr, "ERROR: public and private Ed25519 keys do not match.\n"); + return 1; + } + } + } else { + if(ecdsa_pub) { + fprintf(stderr, "WARNING: A public Ed25519 key was found but no private key is known.\n"); + } + } + + // Check whether scripts are executable + + struct dirent *ent; + DIR *dir = opendir(confbase); + + if(!dir) { + fprintf(stderr, "ERROR: cannot read directory %s: %s\n", confbase, strerror(errno)); + return 1; + } + + while((ent = readdir(dir))) { + if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) { + continue; + } + + strncpy(fname, ent->d_name, sizeof(fname)); + char *dash = strrchr(fname, '-'); + + if(!dash) { + continue; + } + + *dash = 0; + + if(strcmp(fname, "tinc") && strcmp(fname, "host") && strcmp(fname, "subnet")) { + static bool explained = false; + fprintf(stderr, "WARNING: Unknown script %s" SLASH "%s found.\n", confbase, ent->d_name); + + if(!explained) { + fprintf(stderr, "The only scripts in %s executed by tinc are:\n", confbase); + fprintf(stderr, "tinc-up, tinc-down, host-up, host-down, subnet-up and subnet-down.\n"); + explained = true; + } + + continue; + } + + snprintf(fname, sizeof(fname), "%s" SLASH "%s", confbase, ent->d_name); + + if(access(fname, R_OK | X_OK)) { + if(errno != EACCES) { + fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno)); + continue; + } + + fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno)); + + if(ask_fix()) { + if(chmod(fname, 0755)) { + fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno)); + } + } + } + } + + closedir(dir); + + snprintf(dname, sizeof(dname), "%s" SLASH "hosts", confbase); + dir = opendir(dname); + + if(!dir) { + fprintf(stderr, "ERROR: cannot read directory %s: %s\n", dname, strerror(errno)); + return 1; + } + + while((ent = readdir(dir))) { + if(strtailcmp(ent->d_name, "-up") && strtailcmp(ent->d_name, "-down")) { + continue; + } + + strncpy(fname, ent->d_name, sizeof(fname)); + char *dash = strrchr(fname, '-'); + + if(!dash) { + continue; + } + + *dash = 0; + + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name); + + if(access(fname, R_OK | X_OK)) { + if(errno != EACCES) { + fprintf(stderr, "ERROR: cannot access %s: %s\n", fname, strerror(errno)); + continue; + } + + fprintf(stderr, "WARNING: cannot read and execute %s: %s\n", fname, strerror(errno)); + + if(ask_fix()) { + if(chmod(fname, 0755)) { + fprintf(stderr, "ERROR: cannot change permissions on %s: %s\n", fname, strerror(errno)); + } + } + } + } + + closedir(dir); + + // Check for obsolete / unsafe / unknown configuration variables. + + check_conffile(tinc_conf, true); + + dir = opendir(dname); + + if(dir) { + while((ent = readdir(dir))) { + if(!check_id(ent->d_name)) { + continue; + } + + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name); + check_conffile(fname, false); + } + + closedir(dir); + } + + return 0; +} + diff --git a/src/fsck.h b/src/fsck.h new file mode 100644 index 0000000..3e0b4ca --- /dev/null +++ b/src/fsck.h @@ -0,0 +1,25 @@ +#ifndef TINC_FSCK_H +#define TINC_FSCK_H + +/* + fsck.h -- header for fsck.c. + Copyright (C) 2012 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. +*/ + +extern int fsck(const char *argv0); + +#endif diff --git a/src/gcrypt/cipher.c b/src/gcrypt/cipher.c new file mode 100644 index 0000000..3eed8e9 --- /dev/null +++ b/src/gcrypt/cipher.c @@ -0,0 +1,284 @@ +/* + cipher.c -- Symmetric block cipher handling + Copyright (C) 2007-2012 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 "cipher.h" +#include "logger.h" +#include "xalloc.h" + +static struct { + const char *name; + int algo; + int mode; + int nid; +} ciphertable[] = { + {"none", GCRY_CIPHER_NONE, GCRY_CIPHER_MODE_NONE, 0}, + + {NULL, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_ECB, 92}, + {"blowfish", GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_CBC, 91}, + {NULL, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_CFB, 93}, + {NULL, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_OFB, 94}, + + {NULL, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_ECB, 418}, + {"aes", GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 419}, + {NULL, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CFB, 421}, + {NULL, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_OFB, 420}, + + {NULL, GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_ECB, 422}, + {"aes192", GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CBC, 423}, + {NULL, GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_CFB, 425}, + {NULL, GCRY_CIPHER_AES192, GCRY_CIPHER_MODE_OFB, 424}, + + {NULL, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_ECB, 426}, + {"aes256", GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CBC, 427}, + {NULL, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CFB, 429}, + {NULL, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_OFB, 428}, +}; + +static bool nametocipher(const char *name, int *algo, int *mode) { + size_t i; + + for(i = 0; i < sizeof(ciphertable) / sizeof(*ciphertable); i++) { + if(ciphertable[i].name && !strcasecmp(name, ciphertable[i].name)) { + *algo = ciphertable[i].algo; + *mode = ciphertable[i].mode; + return true; + } + } + + return false; +} + +static bool nidtocipher(int nid, int *algo, int *mode) { + size_t i; + + for(i = 0; i < sizeof(ciphertable) / sizeof(*ciphertable); i++) { + if(nid == ciphertable[i].nid) { + *algo = ciphertable[i].algo; + *mode = ciphertable[i].mode; + return true; + } + } + + return false; +} + +static bool ciphertonid(int algo, int mode, int *nid) { + size_t i; + + for(i = 0; i < sizeof(ciphertable) / sizeof(*ciphertable); i++) { + if(algo == ciphertable[i].algo && mode == ciphertable[i].mode) { + *nid = ciphertable[i].nid; + return true; + } + } + + return false; +} + +static bool cipher_open(cipher_t *cipher, int algo, int mode) { + gcry_error_t err; + + if(!ciphertonid(algo, mode, &cipher->nid)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Cipher %d mode %d has no corresponding nid!", algo, mode); + return false; + } + + if((err = gcry_cipher_open(&cipher->handle, algo, mode, 0))) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unable to initialise cipher %d mode %d: %s", algo, mode, gcry_strerror(err)); + return false; + } + + cipher->keylen = gcry_cipher_get_algo_keylen(algo); + cipher->blklen = gcry_cipher_get_algo_blklen(algo); + cipher->key = xmalloc(cipher->keylen + cipher->blklen); + cipher->padding = mode == GCRY_CIPHER_MODE_ECB || mode == GCRY_CIPHER_MODE_CBC; + + return true; +} + +bool cipher_open_by_name(cipher_t *cipher, const char *name) { + int algo, mode; + + if(!nametocipher(name, &algo, &mode)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown cipher name '%s'!", name); + return false; + } + + return cipher_open(cipher, algo, mode); +} + +bool cipher_open_by_nid(cipher_t *cipher, int nid) { + int algo, mode; + + if(!nidtocipher(nid, &algo, &mode)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown cipher ID %d!", nid); + return false; + } + + return cipher_open(cipher, algo, mode); +} + +bool cipher_open_blowfish_ofb(cipher_t *cipher) { + return cipher_open(cipher, GCRY_CIPHER_BLOWFISH, GCRY_CIPHER_MODE_OFB); +} + +void cipher_close(cipher_t *cipher) { + if(cipher->handle) { + gcry_cipher_close(cipher->handle); + cipher->handle = NULL; + } + + free(cipher->key); + cipher->key = NULL; +} + +size_t cipher_keylength(const cipher_t *cipher) { + return cipher->keylen + cipher->blklen; +} + +void cipher_get_key(const cipher_t *cipher, void *key) { + memcpy(key, cipher->key, cipher->keylen + cipher->blklen); +} + +bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) { + memcpy(cipher->key, key, cipher->keylen + cipher->blklen); + + gcry_cipher_setkey(cipher->handle, cipher->key, cipher->keylen); + gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); + + return true; +} + +bool cipher_set_key_from_rsa(cipher_t *cipher, void *key, size_t len, bool encrypt) { + memcpy(cipher->key, key + len - cipher->keylen, cipher->keylen + cipher->blklen); + memcpy(cipher->key + cipher->keylen, key + len - cipher->keylen - cipher->blklen, cipher->blklen); + + gcry_cipher_setkey(cipher->handle, cipher->key, cipher->keylen); + gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); + + return true; +} + +bool cipher_regenerate_key(cipher_t *cipher, bool encrypt) { + gcry_create_nonce(cipher->key, cipher->keylen + cipher->blklen); + + gcry_cipher_setkey(cipher->handle, cipher->key, cipher->keylen); + gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); + + return true; +} + +bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + gcry_error_t err; + uint8_t pad[cipher->blklen]; + + if(cipher->padding) { + if(!oneshot) { + return false; + } + + size_t reqlen = ((inlen + cipher->blklen) / cipher->blklen) * cipher->blklen; + + if(*outlen < reqlen) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: not enough room for padding"); + return false; + } + + uint8_t padbyte = reqlen - inlen; + inlen = reqlen - cipher->blklen; + + for(int i = 0; i < cipher->blklen; i++) + if(i < cipher->blklen - padbyte) { + pad[i] = ((uint8_t *)indata)[inlen + i]; + } else { + pad[i] = padbyte; + } + } + + if(oneshot) { + gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); + } + + if((err = gcry_cipher_encrypt(cipher->handle, outdata, *outlen, indata, inlen))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); + return false; + } + + if(cipher->padding) { + if((err = gcry_cipher_encrypt(cipher->handle, outdata + inlen, cipher->blklen, pad, cipher->blklen))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); + return false; + } + + inlen += cipher->blklen; + } + + *outlen = inlen; + return true; +} + +bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + gcry_error_t err; + + if(oneshot) { + gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); + } + + if((err = gcry_cipher_decrypt(cipher->handle, outdata, *outlen, indata, inlen))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", gcry_strerror(err)); + return false; + } + + if(cipher->padding) { + if(!oneshot) { + return false; + } + + uint8_t padbyte = ((uint8_t *)outdata)[inlen - 1]; + + if(padbyte == 0 || padbyte > cipher->blklen || padbyte > inlen) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: invalid padding"); + return false; + } + + size_t origlen = inlen - padbyte; + + for(int i = inlen - 1; i >= origlen; i--) + if(((uint8_t *)outdata)[i] != padbyte) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: invalid padding"); + return false; + } + + *outlen = origlen; + } else { + *outlen = inlen; + } + + return true; +} + +int cipher_get_nid(const cipher_t *cipher) { + return cipher->nid; +} + +bool cipher_active(const cipher_t *cipher) { + return cipher->nid != 0; +} diff --git a/src/gcrypt/crypto.c b/src/gcrypt/crypto.c new file mode 100644 index 0000000..dc92b3e --- /dev/null +++ b/src/gcrypt/crypto.c @@ -0,0 +1,34 @@ +/* + crypto.c -- Cryptographic miscellaneous functions and initialisation + Copyright (C) 2007 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 "crypto.h" + +void crypto_init() { +} + +void crypto_exit() { +} + +void randomize(void *out, size_t outlen) { + gcry_create_nonce(out, outlen); +} diff --git a/src/gcrypt/digest.c b/src/gcrypt/digest.c new file mode 100644 index 0000000..fce48ee --- /dev/null +++ b/src/gcrypt/digest.c @@ -0,0 +1,182 @@ +/* + digest.c -- Digest handling + Copyright (C) 2007-2012 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 "digest.h" +#include "logger.h" + +static struct { + const char *name; + int algo; + int nid; +} digesttable[] = { + {"none", GCRY_MD_NONE, 0}, + {"sha1", GCRY_MD_SHA1, 64}, + {"sha256", GCRY_MD_SHA256, 672}, + {"sha384", GCRY_MD_SHA384, 673}, + {"sha512", GCRY_MD_SHA512, 674}, +}; + +static bool nametodigest(const char *name, int *algo) { + int i; + + for(i = 0; i < sizeof(digesttable) / sizeof(*digesttable); i++) { + if(digesttable[i].name && !strcasecmp(name, digesttable[i].name)) { + *algo = digesttable[i].algo; + return true; + } + } + + return false; +} + +static bool nidtodigest(int nid, int *algo) { + int i; + + for(i = 0; i < sizeof(digesttable) / sizeof(*digesttable); i++) { + if(nid == digesttable[i].nid) { + *algo = digesttable[i].algo; + return true; + } + } + + return false; +} + +static bool digesttonid(int algo, int *nid) { + int i; + + for(i = 0; i < sizeof(digesttable) / sizeof(*digesttable); i++) { + if(algo == digesttable[i].algo) { + *nid = digesttable[i].nid; + return true; + } + } + + return false; +} + +static bool digest_open(digest_t *digest, int algo, int maclength) { + if(!digesttonid(algo, &digest->nid)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Digest %d has no corresponding nid!", algo); + return false; + } + + unsigned int len = gcry_md_get_algo_dlen(algo); + + if(maclength > len || maclength < 0) { + digest->maclength = len; + } else { + digest->maclength = maclength; + } + + digest->algo = algo; + digest->hmac = NULL; + + return true; +} + +bool digest_open_by_name(digest_t *digest, const char *name, int maclength) { + int algo; + + if(!nametodigest(name, &algo)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest name '%s'!", name); + return false; + } + + return digest_open(digest, algo, maclength); +} + +bool digest_open_by_nid(digest_t *digest, int nid, int maclength) { + int algo; + + if(!nidtodigest(nid, &algo)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest ID %d!", nid); + return false; + } + + return digest_open(digest, algo, maclength); +} + +bool digest_open_sha1(digest_t *digest, int maclength) { + return digest_open(digest, GCRY_MD_SHA1, maclength); +} + +void digest_close(digest_t *digest) { + if(digest->hmac) { + gcry_md_close(digest->hmac); + } + + digest->hmac = NULL; +} + +bool digest_set_key(digest_t *digest, const void *key, size_t len) { + if(!digest->hmac) { + gcry_md_open(&digest->hmac, digest->algo, GCRY_MD_FLAG_HMAC); + } + + if(!digest->hmac) { + return false; + } + + return !gcry_md_setkey(digest->hmac, key, len); +} + +bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) { + unsigned int len = gcry_md_get_algo_dlen(digest->algo); + + if(digest->hmac) { + char *tmpdata; + gcry_md_reset(digest->hmac); + gcry_md_write(digest->hmac, indata, inlen); + tmpdata = gcry_md_read(digest->hmac, digest->algo); + + if(!tmpdata) { + return false; + } + + memcpy(outdata, tmpdata, digest->maclength); + } else { + char tmpdata[len]; + gcry_md_hash_buffer(digest->algo, tmpdata, indata, inlen); + memcpy(outdata, tmpdata, digest->maclength); + } + + return true; +} + +bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *cmpdata) { + unsigned int len = digest->maclength; + char outdata[len]; + + return digest_create(digest, indata, inlen, outdata) && !memcmp(cmpdata, outdata, len); +} + +int digest_get_nid(const digest_t *digest) { + return digest->nid; +} + +size_t digest_length(const digest_t *digest) { + return digest->maclength; +} + +bool digest_active(const digest_t *digest) { + return digest->algo != GCRY_MD_NONE; +} diff --git a/src/gcrypt/digest.h b/src/gcrypt/digest.h new file mode 100644 index 0000000..42404fc --- /dev/null +++ b/src/gcrypt/digest.h @@ -0,0 +1,45 @@ +#ifndef TINC_GCRYPT_DIGEST_H +#define TINC_GCRYPT_DIGEST_H + +/* + digest.h -- header file digest.c + Copyright (C) 2007-2009 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 + +#define DIGEST_MAX_SIZE 64 + +typedef struct digest { + int algo; + int nid; + int maclength; + gcry_md_hd_t hmac; +} digest_t; + +extern bool digest_open_by_name(struct digest *, const char *name, int maclength); +extern bool digest_open_by_nid(struct digest *, int nid, int maclength); +extern bool digest_open_sha1(struct digest *, int maclength); +extern void digest_close(struct digest *); +extern bool digest_create(struct digest *, const void *indata, size_t inlen, void *outdata); +extern bool digest_verify(struct digest *, const void *indata, size_t inlen, const void *digestdata); +extern bool digest_set_key(struct digest *, const void *key, size_t len); +extern int digest_get_nid(const struct digest *); +extern size_t digest_length(const struct digest *); +extern bool digest_active(const struct digest *); + +#endif diff --git a/src/gcrypt/prf.c b/src/gcrypt/prf.c new file mode 100644 index 0000000..ce80f8c --- /dev/null +++ b/src/gcrypt/prf.c @@ -0,0 +1,121 @@ +/* + prf.c -- Pseudo-Random Function for key material generation + Copyright (C) 2011-2013 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 "../prf.h" +#include "../ed25519/sha512.h" + +static void memxor(char *buf, char c, size_t len) { + for(size_t i = 0; i < len; i++) { + buf[i] ^= c; + } +} + +static const size_t mdlen = 64; +static const size_t blklen = 128; + +static bool hmac_sha512(const char *key, size_t keylen, const char *msg, size_t msglen, char *out) { + char tmp[blklen + mdlen]; + sha512_context md; + + if(keylen <= blklen) { + memcpy(tmp, key, keylen); + memset(tmp + keylen, 0, blklen - keylen); + } else { + if(sha512(key, keylen, tmp) != 0) { + return false; + } + + memset(tmp + mdlen, 0, blklen - mdlen); + } + + if(sha512_init(&md) != 0) { + return false; + } + + // ipad + memxor(tmp, 0x36, blklen); + + if(sha512_update(&md, tmp, blklen) != 0) { + return false; + } + + // message + if(sha512_update(&md, msg, msglen) != 0) { + return false; + } + + if(sha512_final(&md, tmp + blklen) != 0) { + return false; + } + + // opad + memxor(tmp, 0x36 ^ 0x5c, blklen); + + if(sha512(tmp, sizeof(tmp), out) != 0) { + return false; + } + + return true; +} + + +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5. + We use SHA512 instead of MD5 and SHA1. + */ + +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + /* Data is what the "inner" HMAC function processes. + It consists of the previous HMAC result plus the seed. + */ + + char data[mdlen + seedlen]; + memset(data, 0, mdlen); + memcpy(data + mdlen, seed, seedlen); + + char hash[mdlen]; + + while(outlen > 0) { + /* Inner HMAC */ + if(!hmac_sha512(secret, secretlen, data, sizeof(data), data)) { + return false; + } + + /* Outer HMAC */ + if(outlen >= mdlen) { + if(!hmac_sha512(secret, secretlen, data, sizeof(data), out)) { + return false; + } + + out += mdlen; + outlen -= mdlen; + } else { + if(!hmac_sha512(secret, secretlen, data, sizeof(data), hash)) { + return false; + } + + memcpy(out, hash, outlen); + out += outlen; + outlen = 0; + } + } + + return true; +} diff --git a/src/gcrypt/rsa.c b/src/gcrypt/rsa.c new file mode 100644 index 0000000..eb9f58d --- /dev/null +++ b/src/gcrypt/rsa.c @@ -0,0 +1,327 @@ +/* + rsa.c -- RSA key handling + Copyright (C) 2007-2012 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 "logger.h" +#include "rsa.h" + +// Base64 decoding table + +static const uint8_t b64d[128] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, + 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, + 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, + 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, + 0xff, 0xff +}; + +// PEM encoding/decoding functions + +static bool pem_decode(FILE *fp, const char *header, uint8_t *buf, size_t size, size_t *outsize) { + bool decode = false; + char line[1024]; + uint16_t word = 0; + int shift = 10; + size_t i, j = 0; + + while(!feof(fp)) { + if(!fgets(line, sizeof(line), fp)) { + return false; + } + + if(!decode && !strncmp(line, "-----BEGIN ", 11)) { + if(!strncmp(line + 11, header, strlen(header))) { + decode = true; + } + + continue; + } + + if(decode && !strncmp(line, "-----END", 8)) { + break; + } + + if(!decode) { + continue; + } + + for(i = 0; line[i] >= ' '; i++) { + if((signed char)line[i] < 0 || b64d[(int)line[i]] == 0xff) { + break; + } + + word |= b64d[(int)line[i]] << shift; + shift -= 6; + + if(shift <= 2) { + if(j > size) { + errno = ENOMEM; + return false; + } + + buf[j++] = word >> 8; + word <<= 8; + shift += 8; + } + } + } + + if(outsize) { + *outsize = j; + } + + return true; +} + + +// BER decoding functions + +static int ber_read_id(unsigned char **p, size_t *buflen) { + if(*buflen <= 0) { + return -1; + } + + if((**p & 0x1f) == 0x1f) { + int id = 0; + bool more; + + while(*buflen > 0) { + id <<= 7; + id |= **p & 0x7f; + more = *(*p)++ & 0x80; + (*buflen)--; + + if(!more) { + break; + } + } + + return id; + } else { + (*buflen)--; + return *(*p)++ & 0x1f; + } +} + +static size_t ber_read_len(unsigned char **p, size_t *buflen) { + if(*buflen <= 0) { + return -1; + } + + if(**p & 0x80) { + size_t result = 0; + int len = *(*p)++ & 0x7f; + (*buflen)--; + + if(len > *buflen) { + return 0; + } + + while(len--) { + result <<= 8; + result |= *(*p)++; + (*buflen)--; + } + + return result; + } else { + (*buflen)--; + return *(*p)++; + } +} + + +static bool ber_read_sequence(unsigned char **p, size_t *buflen, size_t *result) { + int tag = ber_read_id(p, buflen); + size_t len = ber_read_len(p, buflen); + + if(tag == 0x10) { + if(result) { + *result = len; + } + + return true; + } else { + return false; + } +} + +static bool ber_read_mpi(unsigned char **p, size_t *buflen, gcry_mpi_t *mpi) { + int tag = ber_read_id(p, buflen); + size_t len = ber_read_len(p, buflen); + gcry_error_t err = 0; + + if(tag != 0x02 || len > *buflen) { + return false; + } + + if(mpi) { + err = gcry_mpi_scan(mpi, GCRYMPI_FMT_USG, *p, len, NULL); + } + + *p += len; + *buflen -= len; + + return mpi ? !err : true; +} + +bool rsa_set_hex_public_key(rsa_t *rsa, char *n, char *e) { + gcry_error_t err = 0; + + err = gcry_mpi_scan(&rsa->n, GCRYMPI_FMT_HEX, n, 0, NULL) + ? : gcry_mpi_scan(&rsa->e, GCRYMPI_FMT_HEX, e, 0, NULL); + + if(err) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); + return false; + } + + return true; +} + +bool rsa_set_hex_private_key(rsa_t *rsa, char *n, char *e, char *d) { + gcry_error_t err = 0; + + err = gcry_mpi_scan(&rsa->n, GCRYMPI_FMT_HEX, n, 0, NULL) + ? : gcry_mpi_scan(&rsa->e, GCRYMPI_FMT_HEX, e, 0, NULL) + ? : gcry_mpi_scan(&rsa->d, GCRYMPI_FMT_HEX, d, 0, NULL); + + if(err) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); + return false; + } + + return true; +} + +// Read PEM RSA keys + +bool rsa_read_pem_public_key(rsa_t *rsa, FILE *fp) { + uint8_t derbuf[8096], *derp = derbuf; + size_t derlen; + + if(!pem_decode(fp, "RSA PUBLIC KEY", derbuf, sizeof(derbuf), &derlen)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA public key: %s", strerror(errno)); + return NULL; + } + + if(!ber_read_sequence(&derp, &derlen, NULL) + || !ber_read_mpi(&derp, &derlen, &rsa->n) + || !ber_read_mpi(&derp, &derlen, &rsa->e) + || derlen) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decoding RSA public key"); + return NULL; + } + + return true; +} + +bool rsa_read_pem_private_key(rsa_t *rsa, FILE *fp) { + uint8_t derbuf[8096], *derp = derbuf; + size_t derlen; + + if(!pem_decode(fp, "RSA PRIVATE KEY", derbuf, sizeof(derbuf), &derlen)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA private key: %s", strerror(errno)); + return NULL; + } + + if(!ber_read_sequence(&derp, &derlen, NULL) + || !ber_read_mpi(&derp, &derlen, NULL) + || !ber_read_mpi(&derp, &derlen, &rsa->n) + || !ber_read_mpi(&derp, &derlen, &rsa->e) + || !ber_read_mpi(&derp, &derlen, &rsa->d) + || !ber_read_mpi(&derp, &derlen, NULL) // p + || !ber_read_mpi(&derp, &derlen, NULL) // q + || !ber_read_mpi(&derp, &derlen, NULL) + || !ber_read_mpi(&derp, &derlen, NULL) + || !ber_read_mpi(&derp, &derlen, NULL) // u + || derlen) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decoding RSA private key"); + return NULL; + } + + return true; +} + +size_t rsa_size(rsa_t *rsa) { + return (gcry_mpi_get_nbits(rsa->n) + 7) / 8; +} + +/* Well, libgcrypt has functions to handle RSA keys, but they suck. + * So we just use libgcrypt's mpi functions, and do the math ourselves. + */ + +// TODO: get rid of this macro, properly clean up gcry_ structures after use +#define check(foo) { gcry_error_t err = (foo); if(err) {logger(DEBUG_ALWAYS, LOG_ERR, "gcrypt error %s/%s at %s:%d", gcry_strsource(err), gcry_strerror(err), __FILE__, __LINE__); return false; }} + +bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) { + gcry_mpi_t inmpi; + check(gcry_mpi_scan(&inmpi, GCRYMPI_FMT_USG, in, len, NULL)); + + gcry_mpi_t outmpi = gcry_mpi_new(len * 8); + gcry_mpi_powm(outmpi, inmpi, rsa->e, rsa->n); + + int pad = len - (gcry_mpi_get_nbits(outmpi) + 7) / 8; + + while(pad--) { + *(char *)out++ = 0; + } + + check(gcry_mpi_print(GCRYMPI_FMT_USG, out, len, NULL, outmpi)); + + return true; +} + +bool rsa_private_decrypt(rsa_t *rsa, void *in, size_t len, void *out) { + gcry_mpi_t inmpi; + check(gcry_mpi_scan(&inmpi, GCRYMPI_FMT_USG, in, len, NULL)); + + gcry_mpi_t outmpi = gcry_mpi_new(len * 8); + gcry_mpi_powm(outmpi, inmpi, rsa->d, rsa->n); + + int pad = len - (gcry_mpi_get_nbits(outmpi) + 7) / 8; + + while(pad--) { + *(char *)out++ = 0; + } + + check(gcry_mpi_print(GCRYMPI_FMT_USG, out, len, NULL, outmpi)); + + return true; +} diff --git a/src/gcrypt/rsagen.c b/src/gcrypt/rsagen.c new file mode 100644 index 0000000..030f2bc --- /dev/null +++ b/src/gcrypt/rsagen.c @@ -0,0 +1,239 @@ +/* + rsagen.c -- RSA key generation and export + Copyright (C) 2008-2012 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 "rsagen.h" + +#if 0 +// Base64 encoding table + +static const char b64e[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// PEM encoding + +static bool pem_encode(FILE *fp, const char *header, uint8_t *buf, size_t size) { + bool decode = false; + char line[1024]; + uint32_t word = 0; + int shift = 0; + size_t i, j = 0; + + fprintf(fp, "-----BEGIN %s-----\n", header); + + for(i = 0; i < size; i += 3) { + if(i <= size - 3) { + word = buf[i] << 16 | buf[i + 1] << 8 | buf[i + 2]; + } else { + word = buf[i] << 16; + + if(i == size - 2) { + word |= buf[i + 1] << 8; + } + } + + line[j++] = b64e[(word >> 18) ]; + line[j++] = b64e[(word >> 12) & 0x3f]; + line[j++] = b64e[(word >> 6) & 0x3f]; + line[j++] = b64e[(word) & 0x3f]; + + if(j >= 64) { + line[j++] = '\n'; + line[j] = 0; + fputs(line, fp); + j = 0; + } + } + + if(size % 3 > 0) { + if(size % 3 > 1) { + line[j++] = '='; + } + + line[j++] = '='; + } + + if(j) { + line[j++] = '\n'; + line[j] = 0; + fputs(line, fp); + } + + fprintf(fp, "-----END %s-----\n", header); + + return true; +} + + +// BER encoding functions + +static bool ber_write_id(uint8_t **p, size_t *buflen, int id) { + if(*buflen <= 0) { + return false; + } + + if(id >= 0x1f) { + while(id) { + if(*buflen <= 0) { + return false; + } + + (*buflen)--; + **p = id & 0x7f; + id >>= 7; + + if(id) { + **p |= 0x80; + } + + (*p)++; + } + } else { + (*buflen)--; + *(*p)++ = id; + } + + return true; +} + +static bool ber_write_len(uint8_t **p, size_t *buflen, size_t len) { + do { + if(*buflen <= 0) { + return false; + } + + (*buflen)--; + **p = len & 0x7f; + len >>= 7; + + if(len) { + **p |= 0x80; + } + + (*p)++; + } while(len); + + return true; +} + +static bool ber_write_sequence(uint8_t **p, size_t *buflen, uint8_t *seqbuf, size_t seqlen) { + if(!ber_write_id(p, buflen, 0x10) || !ber_write_len(p, buflen, seqlen) || *buflen < seqlen) { + return false; + } + + memcpy(*p, seqbuf, seqlen); + *p += seqlen; + *buflen -= seqlen; + + return true; +} + +static bool ber_write_mpi(uint8_t **p, size_t *buflen, gcry_mpi_t mpi) { + uint8_t tmpbuf[1024]; + size_t tmplen = sizeof(tmpbuf); + gcry_error_t err; + + err = gcry_mpi_aprint(GCRYMPI_FMT_USG, &tmpbuf, &tmplen, mpi); + + if(err) { + return false; + } + + if(!ber_write_id(p, buflen, 0x02) || !ber_write_len(p, buflen, tmplen) || *buflen < tmplen) { + return false; + } + + memcpy(*p, tmpbuf, tmplen); + *p += tmplen; + *buflen -= tmplen; + + return true; +} + +// Write PEM RSA keys + +bool rsa_write_pem_public_key(rsa_t *rsa, FILE *fp) { + uint8_t derbuf1[8096]; + uint8_t derbuf2[8096]; + uint8_t *derp1 = derbuf1; + uint8_t *derp2 = derbuf2; + size_t derlen1 = sizeof(derbuf1); + size_t derlen2 = sizeof(derbuf2); + + if(!ber_write_mpi(&derp1, &derlen1, &rsa->n) + || !ber_write_mpi(&derp1, &derlen1, &rsa->e) + || !ber_write_sequence(&derp2, &derlen2, derbuf1, derlen1)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encoding RSA public key"); + return false; + } + + if(!pem_encode(fp, "RSA PUBLIC KEY", derbuf2, derlen2)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to write RSA public key: %s", strerror(errno)); + return false; + } + + return true; +} + +bool rsa_write_pem_private_key(rsa_t *rsa, FILE *fp) { + uint8_t derbuf1[8096]; + uint8_t derbuf2[8096]; + uint8_t *derp1 = derbuf1; + uint8_t *derp2 = derbuf2; + size_t derlen1 = sizeof(derbuf1); + size_t derlen2 = sizeof(derbuf2); + + if(!ber_write_mpi(&derp1, &derlen1, &bits) + || ber_write_mpi(&derp1, &derlen1, &rsa->n) // modulus + || ber_write_mpi(&derp1, &derlen1, &rsa->e) // public exponent + || ber_write_mpi(&derp1, &derlen1, &rsa->d) // private exponent + || ber_write_mpi(&derp1, &derlen1, &p) + || ber_write_mpi(&derp1, &derlen1, &q) + || ber_write_mpi(&derp1, &derlen1, &exp1) + || ber_write_mpi(&derp1, &derlen1, &exp2) + || ber_write_mpi(&derp1, &derlen1, &coeff)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encoding RSA private key"); + } + + return false; +} + +if(!pem_encode(fp, "RSA PRIVATE KEY", derbuf2, derlen2)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to write RSA private key: %s", strerror(errno)); + return false; +} + +return true; +} +#endif + +bool rsa_write_pem_public_key(rsa_t *rsa, FILE *fp) { + return false; +} + +bool rsa_write_pem_private_key(rsa_t *rsa, FILE *fp) { + return false; +} + +bool rsa_generate(rsa_t *rsa, size_t bits, unsigned long exponent) { + fprintf(stderr, "Generating RSA keys with libgcrypt not implemented yet\n"); + return false; +} diff --git a/src/getopt.c b/src/getopt.c index 741c7f2..5d5fb59 100644 --- a/src/getopt.c +++ b/src/getopt.c @@ -132,7 +132,7 @@ int optind = 1; causes problems with re-calling getopt as programs generally don't know that. */ -int getopt_initialized = 0; +int __getopt_initialized = 0; /* The next char to be scanned in the option-element in which the last option character we returned was found. @@ -248,7 +248,7 @@ static int last_nonopt; indicating ARGV elements that should not be considered arguments. */ /* Defined in getopt_init.c */ -extern char *getopt_nonoption_flags; +extern char *__getopt_nonoption_flags; static int nonoption_flags_max_len; static int nonoption_flags_len; @@ -256,7 +256,7 @@ static int nonoption_flags_len; static int original_argc; static char *const *original_argv; -extern pid_t libc_pid; +extern pid_t __libc_pid; /* Make sure the environment variable bash 2.0 puts in the environment is valid for the getopt call we must make sure that the ARGV passed @@ -269,14 +269,14 @@ store_args_and_env(int argc, char *const *argv) { original_argc = argc; original_argv = argv; } -text_set_element(libc_subinit, store_args_and_env); +text_set_element(__libc_subinit, store_args_and_env); # define SWAP_FLAGS(ch1, ch2) \ if (nonoption_flags_len > 0) \ { \ - char tmp = getopt_nonoption_flags[ch1]; \ - getopt_nonoption_flags[ch1] = getopt_nonoption_flags[ch2]; \ - getopt_nonoption_flags[ch2] = tmp; \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ } #else /* !_LIBC */ # define SWAP_FLAGS(ch1, ch2) @@ -311,7 +311,7 @@ char **argv; #ifdef _LIBC - /* First make sure the handling of the `getopt_nonoption_flags' + /* First make sure the handling of the `__getopt_nonoption_flags' string can work normally. Our top argument must be in the range of the string. */ if(nonoption_flags_len > 0 && top >= nonoption_flags_max_len) { @@ -322,11 +322,11 @@ char **argv; if(new_str == NULL) { nonoption_flags_len = nonoption_flags_max_len = 0; } else { - memcpy(new_str, getopt_nonoption_flags, nonoption_flags_max_len); + memcpy(new_str, __getopt_nonoption_flags, nonoption_flags_max_len); memset(&new_str[nonoption_flags_max_len], '\0', top + 1 - nonoption_flags_max_len); nonoption_flags_max_len = top + 1; - getopt_nonoption_flags = new_str; + __getopt_nonoption_flags = new_str; } } @@ -412,25 +412,25 @@ const char *optstring; if(posixly_correct == NULL && argc == original_argc && argv == original_argv) { if(nonoption_flags_max_len == 0) { - if(getopt_nonoption_flags == NULL - || getopt_nonoption_flags[0] == '\0') { + if(__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') { nonoption_flags_max_len = -1; } else { - const char *orig_str = getopt_nonoption_flags; + const char *orig_str = __getopt_nonoption_flags; int len = nonoption_flags_max_len = strlen(orig_str); if(nonoption_flags_max_len < argc) { nonoption_flags_max_len = argc; } - getopt_nonoption_flags = + __getopt_nonoption_flags = (char *) malloc(nonoption_flags_max_len); - if(getopt_nonoption_flags == NULL) { + if(__getopt_nonoption_flags == NULL) { nonoption_flags_max_len = -1; } else { - memcpy(getopt_nonoption_flags, orig_str, len); - memset(&getopt_nonoption_flags[len], '\0', + memcpy(__getopt_nonoption_flags, orig_str, len); + memset(&__getopt_nonoption_flags[len], '\0', nonoption_flags_max_len - len); } } @@ -513,13 +513,13 @@ int long_only; { optarg = NULL; - if(optind == 0 || !getopt_initialized) { + if(optind == 0 || !__getopt_initialized) { if(optind == 0) { optind = 1; /* Don't scan ARGV[0], the program name. */ } optstring = _getopt_initialize(argc, argv, optstring); - getopt_initialized = 1; + __getopt_initialized = 1; } /* Test whether ARGV[optind] points to a non-option argument. @@ -529,7 +529,7 @@ int long_only; #ifdef _LIBC #define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ || (optind < nonoption_flags_len \ - && getopt_nonoption_flags[optind] == '1')) + && __getopt_nonoption_flags[optind] == '1')) #else #define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') #endif diff --git a/src/getopt.h b/src/getopt.h index ab1d40b..e9a7040 100644 --- a/src/getopt.h +++ b/src/getopt.h @@ -22,76 +22,79 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef cplusplus +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus extern "C" { #endif - /* For communication from `getopt' to the caller. - When `getopt' finds an option that takes an argument, - the argument value is returned here. - Also, when `ordering' is RETURN_IN_ORDER, - each non-option ARGV-element is returned here. */ +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ - extern char *optarg; +extern char *optarg; - /* Index in ARGV of the next element to be scanned. - This is used for communication to and from the caller - and for communication between successive calls to `getopt'. +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. - On entry to `getopt', zero means this is the first call; initialize. + On entry to `getopt', zero means this is the first call; initialize. - When `getopt' returns -1, this is the index of the first of the - non-option elements that the caller should itself scan. + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. - Otherwise, `optind' communicates from one call to the next - how much of ARGV has been scanned so far. */ + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ - extern int optind; +extern int optind; - /* Callers store zero here to inhibit the error message `getopt' prints - for unrecognized options. */ +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ - extern int opterr; +extern int opterr; - /* Set to an option character which was unrecognized. */ +/* Set to an option character which was unrecognized. */ - extern int optopt; +extern int optopt; - /* Describe the long-named options requested by the application. - The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector - of `struct option' terminated by an element containing a name which is - zero. +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. - The field `has_arg' is: - no_argument (or 0) if the option does not take an argument, - required_argument (or 1) if the option requires an argument, - optional_argument (or 2) if the option takes an optional argument. + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. - If the field `flag' is not NULL, it points to a variable that is set - to the value given in the field `val' when the option is found, but - left unchanged if the option is not found. + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. - To have a long-named option do something other than set an `int' to - a compiled-in constant, such as set a value from `optarg', set the - option's `flag' field to zero and its `val' field to a nonzero - value (the equivalent single-letter option character, if there is - one). For long options that have a zero `flag' field, `getopt' - returns the contents of the `val' field. */ + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ - struct option { +struct option { #if defined (__STDC__) && __STDC__ - const char *name; + const char *name; #else - char *name; + char *name; #endif - /* has_arg can't be an enum because some compilers complain about - type mismatches in all the code that assumes it is an int. */ - int has_arg; - int *flag; - int val; - }; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; - /* Names for the values of the `has_arg' field of `struct option'. */ +/* Names for the values of the `has_arg' field of `struct option'. */ #define no_argument 0 #define required_argument 1 @@ -99,33 +102,33 @@ extern "C" { #if defined (__STDC__) && __STDC__ #ifdef __GNU_LIBRARY__ - /* Many other libraries have conflicting prototypes for getopt, with - differences in the consts, in stdlib.h. To avoid compilation - errors, only prototype getopt for the GNU C library. */ - extern int getopt(int argc, char *const *argv, const char *shortopts); +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt(int argc, char *const *argv, const char *shortopts); #else /* not __GNU_LIBRARY__ */ - extern int getopt(); +extern int getopt(); #endif /* __GNU_LIBRARY__ */ - extern int getopt_long(int argc, char *const *argv, const char *shortopts, - const struct option *longopts, int *longind); - extern int getopt_long_only(int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind); +extern int getopt_long(int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only(int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); - /* Internal only. Users should not call this directly. */ - extern int _getopt_internal(int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind, - int long_only); +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal(int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); #else /* not __STDC__ */ - extern int getopt(); - extern int getopt_long(); - extern int getopt_long_only(); +extern int getopt(); +extern int getopt_long(); +extern int getopt_long_only(); - extern int _getopt_internal(); +extern int _getopt_internal(); #endif /* __STDC__ */ -#ifdef cplusplus +#ifdef __cplusplus } #endif diff --git a/src/graph.c b/src/graph.c index c63fdf9..a774eac 100644 --- a/src/graph.c +++ b/src/graph.c @@ -1,6 +1,6 @@ /* graph.c -- graph algorithms - Copyright (C) 2001-2014 Guus Sliepen , + Copyright (C) 2001-2017 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -44,22 +44,21 @@ #include "system.h" -#include "avl_tree.h" -#include "conf.h" #include "connection.h" #include "device.h" #include "edge.h" #include "graph.h" +#include "list.h" #include "logger.h" +#include "names.h" #include "netutl.h" #include "node.h" -#include "process.h" #include "protocol.h" +#include "script.h" #include "subnet.h" #include "utils.h" #include "xalloc.h" - -static bool graph_changed = true; +#include "graph.h" /* Implementation of Kruskal's algorithm. Running time: O(EN) @@ -67,42 +66,23 @@ static bool graph_changed = true; */ static void mst_kruskal(void) { - avl_node_t *node, *next; - edge_t *e; - node_t *n; - connection_t *c; - int nodes = 0; - int safe_edges = 0; - bool skipped; - /* Clear MST status on connections */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + for list_each(connection_t, c, connection_list) { c->status.mst = false; } - /* Do we have something to do at all? */ - - if(!edge_weight_tree->head) { - return; - } - - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:"); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Kruskal's algorithm:"); /* Clear visited status on nodes */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; + for splay_each(node_t, n, node_tree) { n->status.visited = false; - nodes++; } /* Starting point */ - for(node = edge_weight_tree->head; node; node = node->next) { - e = node->data; - + for splay_each(edge_t, e, edge_weight_tree) { if(e->from->status.reachable) { e->from->status.visited = true; break; @@ -111,11 +91,10 @@ static void mst_kruskal(void) { /* Add safe edges */ - for(skipped = false, node = edge_weight_tree->head; node; node = next) { - next = node->next; - e = node->data; + bool skipped = false; - if(!e->reverse || e->from->status.visited == e->to->status.visited) { + for splay_each(edge_t, e, edge_weight_tree) { + if(!e->reverse || (e->from->status.visited == e->to->status.visited)) { skipped = true; continue; } @@ -131,20 +110,13 @@ static void mst_kruskal(void) { e->reverse->connection->status.mst = true; } - safe_edges++; - - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name, - e->to->name, e->weight); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name, e->to->name, e->weight); if(skipped) { skipped = false; next = edge_weight_tree->head; - continue; } } - - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes, - safe_edges); } /* Implementation of a simple breadth-first search algorithm. @@ -152,25 +124,14 @@ static void mst_kruskal(void) { */ static void sssp_bfs(void) { - avl_node_t *node, *next, *to; - edge_t *e; - node_t *n; - list_t *todo_list; - list_node_t *from, *todonext; - bool indirect; - char *name; - char *address, *port; - char *envp[8] = {NULL}; - int i; - - todo_list = list_alloc(NULL); + list_t *todo_list = list_alloc(NULL); /* Clear visited status on nodes */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; + for splay_each(node_t, n, node_tree) { n->status.visited = false; n->status.indirect = true; + n->distance = -1; } /* Begin with myself */ @@ -180,17 +141,20 @@ static void sssp_bfs(void) { myself->nexthop = myself; myself->prevedge = NULL; myself->via = myself; + myself->distance = 0; list_insert_head(todo_list, myself); /* Loop while todo_list is filled */ - for(from = todo_list->head; from; from = todonext) { /* "from" is the node from which we start */ - n = from->data; + for list_each(node_t, n, todo_list) { /* "n" is the node from which we start */ + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Examining edges from %s", n->name); - for(to = n->edge_tree->head; to; to = to->next) { /* "to" is the edge connected to "from" */ - e = to->data; + if(n->distance < 0) { + abort(); + } - if(!e->reverse) { + for splay_each(edge_t, e, n->edge_tree) { /* "e" is the edge connected to "from" */ + if(!e->reverse || e->to == myself) { continue; } @@ -211,16 +175,17 @@ static void sssp_bfs(void) { of nodes behind it. */ - indirect = n->status.indirect || e->options & OPTION_INDIRECT; + bool indirect = n->status.indirect || e->options & OPTION_INDIRECT; if(e->to->status.visited - && (!e->to->status.indirect || indirect)) { + && (!e->to->status.indirect || indirect) + && (e->to->distance != n->distance + 1 || e->weight >= e->to->prevedge->weight)) { continue; } - // Only update nexthop the first time we visit this node. + // Only update nexthop if it doesn't increase the path length - if(!e->to->status.visited) { + if(!e->to->status.visited || (e->to->distance == n->distance + 1 && e->weight >= e->to->prevedge->weight)) { e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop; } @@ -229,74 +194,93 @@ static void sssp_bfs(void) { e->to->prevedge = e; e->to->via = indirect ? n->via : e->to; e->to->options = e->options; + e->to->distance = n->distance + 1; - if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN) { + if(!e->to->status.reachable || (e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)) { update_node_udp(e->to, &e->address); } list_insert_tail(todo_list, e->to); } - todonext = from->next; - list_delete_node(todo_list, from); + next = node->next; /* Because the list_insert_tail() above could have added something extra for us! */ + list_delete_node(todo_list, node); } list_free(todo_list); +} +static void check_reachability(void) { /* Check reachability status. */ - for(node = node_tree->head; node; node = next) { - next = node->next; - n = node->data; + int reachable_count = 0; + int became_reachable_count = 0; + int became_unreachable_count = 0; + for splay_each(node_t, n, node_tree) { if(n->status.visited != n->status.reachable) { n->status.reachable = !n->status.reachable; + n->last_state_change = now.tv_sec; if(n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became reachable", - n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became reachable", + n->name, n->hostname); + + if(n != myself) { + became_reachable_count++; + } } else { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became unreachable", - n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable", + n->name, n->hostname); + + if(n != myself) { + became_unreachable_count++; + } + } + + if(experimental && OPTION_VERSION(n->options) >= 2) { + n->status.sptps = true; } /* TODO: only clear status.validkey if node is unreachable? */ n->status.validkey = false; + + if(n->status.sptps) { + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + } + n->last_req_key = 0; + n->status.udp_confirmed = false; n->maxmtu = MTU; + n->maxrecentlen = 0; n->minmtu = 0; n->mtuprobes = 0; - if(n->mtuevent) { - event_del(n->mtuevent); - n->mtuevent = NULL; - } + timeout_del(&n->udp_ping_timeout); - xasprintf(&envp[0], "NETNAME=%s", netname ? netname : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? device : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? iface : ""); - xasprintf(&envp[3], "NODE=%s", n->name); + char *name; + char *address; + char *port; + + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", n->name); sockaddr2str(&n->address, &address, &port); - xasprintf(&envp[4], "REMOTEADDRESS=%s", address); - xasprintf(&envp[5], "REMOTEPORT=%s", port); - xasprintf(&envp[6], "NAME=%s", myself->name); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "REMOTEPORT=%s", port); - execute_script(n->status.reachable ? "host-up" : "host-down", envp); + execute_script(n->status.reachable ? "host-up" : "host-down", &env); - xasprintf(&name, - n->status.reachable ? "hosts/%s-up" : "hosts/%s-down", - n->name); - execute_script(name, envp); + xasprintf(&name, n->status.reachable ? "hosts/%s-up" : "hosts/%s-down", n->name); + execute_script(name, &env); free(name); free(address); free(port); - - for(i = 0; i < 7; i++) { - free(envp[i]); - } + environment_exit(&env); subnet_update(n, NULL, n->status.reachable); @@ -305,86 +289,30 @@ static void sssp_bfs(void) { memset(&n->status, 0, sizeof(n->status)); n->options = 0; } else if(n->connection) { - send_ans_key(n); + // Speed up UDP probing by sending our key. + if(!n->status.sptps) { + send_ans_key(n); + } } } + + if(n->status.reachable && n != myself) { + reachable_count++; + } + } + + if(device_standby) { + if(reachable_count == 0 && became_unreachable_count > 0) { + device_disable(); + } else if(reachable_count > 0 && reachable_count == became_reachable_count) { + device_enable(); + } } } void graph(void) { subnet_cache_flush(); sssp_bfs(); + check_reachability(); mst_kruskal(); - graph_changed = true; -} - - - -/* Dump nodes and edges to a graphviz file. - - The file can be converted to an image with - dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true -*/ - -void dump_graph(void) { - avl_node_t *node; - node_t *n; - edge_t *e; - char *filename = NULL, *tmpname = NULL; - FILE *file, *pipe = NULL; - - if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename)) { - return; - } - - graph_changed = false; - - ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph"); - - if(filename[0] == '|') { - file = pipe = popen(filename + 1, "w"); - } else { - xasprintf(&tmpname, "%s.new", filename); - file = fopen(tmpname, "w"); - } - - if(!file) { - logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno)); - free(filename); - free(tmpname); - return; - } - - fprintf(file, "digraph {\n"); - - /* dump all nodes first */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; - fprintf(file, " \"%s\" [label = \"%s\"];\n", n->name, n->name); - } - - /* now dump all edges */ - for(node = edge_weight_tree->head; node; node = node->next) { - e = node->data; - fprintf(file, " \"%s\" -> \"%s\";\n", e->from->name, e->to->name); - } - - fprintf(file, "}\n"); - - if(pipe) { - pclose(pipe); - } else { - fclose(file); -#ifdef HAVE_MINGW - unlink(filename); -#endif - - if(rename(tmpname, filename)) { - logger(LOG_ERR, "Could not rename %s to %s: %s\n", tmpname, filename, strerror(errno)); - } - - free(tmpname); - } - - free(filename); } diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..63b8f54 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,128 @@ +/* + hash.c -- hash table management + Copyright (C) 2012-2013 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 "hash.h" +#include "xalloc.h" + +/* Generic hash function */ + +static uint32_t hash_function(const void *p, size_t len) { + const uint8_t *q = p; + uint32_t hash = 0; + + while(true) { + for(int i = len > 4 ? 4 : len; --i;) { + hash += (uint32_t)q[len - i] << (8 * i); + } + + hash *= 0x9e370001UL; // Golden ratio prime. + + if(len <= 4) { + break; + } + + len -= 4; + } + + return hash; +} + +/* Map 32 bits int onto 0..n-1, without throwing away too many bits if n is 2^8 or 2^16 */ + +static uint32_t modulo(uint32_t hash, size_t n) { + if(n == 0x100) { + return (hash >> 24) ^ ((hash >> 16) & 0xff) ^ ((hash >> 8) & 0xff) ^ (hash & 0xff); + } else if(n == 0x10000) { + return (hash >> 16) ^ (hash & 0xffff); + } else { + return hash % n; + } +} + +/* (De)allocation */ + +hash_t *hash_alloc(size_t n, size_t size) { + hash_t *hash = xzalloc(sizeof(*hash)); + hash->n = n; + hash->size = size; + hash->keys = xzalloc(hash->n * hash->size); + hash->values = xzalloc(hash->n * sizeof(*hash->values)); + return hash; +} + +void hash_free(hash_t *hash) { + free(hash->keys); + free(hash->values); + free(hash); +} + +/* Searching and inserting */ + +void hash_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; +} + +void *hash_search(const hash_t *hash, const void *key) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) { + return (void *)hash->values[i]; + } + + return NULL; +} + +void *hash_search_or_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) { + return (void *)hash->values[i]; + } + + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; + return NULL; +} + +/* Deleting */ + +void hash_delete(hash_t *hash, const void *key) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + hash->values[i] = NULL; +} + +/* Utility functions */ + +void hash_clear(hash_t *hash) { + memset(hash->values, 0, hash->n * sizeof(*hash->values)); +} + +void hash_resize(hash_t *hash, size_t n) { + hash->keys = xrealloc(hash->keys, n * hash->size); + hash->values = xrealloc(hash->values, n * sizeof(*hash->values)); + + if(n > hash->n) { + memset(hash->keys + hash->n * hash->size, 0, (n - hash->n) * hash->size); + memset(hash->values + hash->n, 0, (n - hash->n) * sizeof(*hash->values)); + } +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..e4b1499 --- /dev/null +++ b/src/hash.h @@ -0,0 +1,42 @@ +#ifndef TINC_HASH_H +#define TINC_HASH_H + +/* + hash.h -- header file for hash.c + Copyright (C) 2012 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. +*/ + +typedef struct hash_t { + size_t n; + size_t size; + char *keys; + const void **values; +} hash_t; + +extern hash_t *hash_alloc(size_t n, size_t size) __attribute__((__malloc__)); +extern void hash_free(hash_t *); + +extern void hash_insert(hash_t *, const void *key, const void *value); +extern void hash_delete(hash_t *, const void *key); + +extern void *hash_search(const hash_t *, const void *key); +extern void *hash_search_or_insert(hash_t *, const void *key, const void *value); + +extern void hash_clear(hash_t *); +extern void hash_resize(hash_t *, size_t n); + +#endif diff --git a/src/have.h b/src/have.h index 11fa56a..bb23662 100644 --- a/src/have.h +++ b/src/have.h @@ -4,7 +4,7 @@ /* have.h -- include headers which are known to exist Copyright (C) 1998-2005 Ivo Timmermans - 2003-2015 Guus Sliepen + 2003-2021 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 @@ -22,27 +22,23 @@ */ #ifdef HAVE_MINGW -#ifdef WITH_WINDOWS2000 -#define WINVER Windows2000 -#else #define WINVER WindowsXP -#endif +#define WIN32_LEAN_AND_MEAN #endif #include -#include -#include -#include #include #include +#include #include #include +#include #include -#include - #include #include -#include +#include +#include +#include #ifdef HAVE_MINGW #include @@ -51,20 +47,20 @@ #include #endif -#ifdef HAVE_STDBOOL_H -#include -#endif - #ifdef HAVE_TERMIOS_H #include #endif -#ifdef HAVE_ALLOCA_H -#include +#ifdef HAVE_INTTYPES_H +#include #endif /* Include system specific headers */ +#ifdef HAVE_STDDEF_H +#include +#endif + #ifdef HAVE_SYSLOG_H #include #endif @@ -73,9 +69,6 @@ #include #endif -#ifdef HAVE_TIME_H -#include -#endif #ifdef HAVE_SYS_TYPES_H #include @@ -105,8 +98,8 @@ #include #endif -#ifdef HAVE_SYS_UIO_H -#include +#ifdef HAVE_SYS_UN_H +#include #endif #ifdef HAVE_DIRENT_H @@ -198,9 +191,6 @@ #ifdef HAVE_ARPA_NAMESER_H #include -#ifdef STATUS -#undef STATUS -#endif #endif #ifdef HAVE_RESOLV_H @@ -211,4 +201,20 @@ #include #endif +#ifdef HAVE_GETOPT_H +#include +#else +#include "getopt.h" +#endif + +#ifdef STATUS +#undef STATUS +#endif + +#ifdef HAVE_MINGW +#define SLASH "\\" +#else +#define SLASH "/" +#endif + #endif diff --git a/src/ifconfig.c b/src/ifconfig.c new file mode 100644 index 0000000..59aa94c --- /dev/null +++ b/src/ifconfig.c @@ -0,0 +1,306 @@ +/* + ifconfig.c -- Generate platform specific interface configuration commands + Copyright (C) 2016-2018 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 "conf.h" +#include "ifconfig.h" +#include "subnet.h" + +static long start; + +#ifndef HAVE_MINGW +void ifconfig_header(FILE *out) { + fprintf(out, "#!/bin/sh\n"); + start = ftell(out); +} + +void ifconfig_dhcp(FILE *out) { + fprintf(out, "dhclient -nw \"$INTERFACE\"\n"); +} + +void ifconfig_dhcp6(FILE *out) { + fprintf(out, "dhclient -6 -nw \"$INTERFACE\"\n"); +} + +void ifconfig_slaac(FILE *out) { +#ifdef HAVE_LINUX + fprintf(out, "echo 1 >\"/proc/sys/net/ipv6/conf/$INTERFACE/accept_ra\"\n"); + fprintf(out, "echo 1 >\"/proc/sys/net/ipv6/conf/$INTERFACE/autoconf\"\n"); +#else + fprintf(out, "rtsol \"$INTERFACE\" &\n"); +#endif +} + +bool ifconfig_footer(FILE *out) { + if(ftell(out) == start) { + fprintf(out, "echo 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE netmask \n"); + return false; + } else { +#ifdef HAVE_LINUX + fprintf(out, "ip link set \"$INTERFACE\" up\n"); +#else + fprintf(out, "ifconfig \"$INTERFACE\" up\n"); +#endif + return true; + } +} +#else +void ifconfig_header(FILE *out) { + start = ftell(out); +} + +void ifconfig_dhcp(FILE *out) { + fprintf(out, "netsh interface ipv4 set address \"%%INTERFACE%%\" dhcp\n"); +} + +void ifconfig_dhcp6(FILE *out) { + (void)out; + fprintf(stderr, "DHCPv6 requested, but not supported by tinc on this platform\n"); +} + +void ifconfig_slaac(FILE *out) { + (void)out; + // It's the default? +} + +bool ifconfig_footer(FILE *out) { + return ftell(out) != start; +} +#endif + +static subnet_t ipv4, ipv6; + +void ifconfig_address(FILE *out, const char *value) { + subnet_t address = {0}; + char address_str[MAXNETSTR]; + + if(!str2net(&address, value) || !net2str(address_str, sizeof(address_str), &address)) { + fprintf(stderr, "Could not parse address in Ifconfig statement\n"); + return; + } + + switch(address.type) { + case SUBNET_IPV4: + ipv4 = address; + break; + + case SUBNET_IPV6: + ipv6 = address; + break; + + default: + return; + } + +#if defined(HAVE_LINUX) + + switch(address.type) { + case SUBNET_MAC: + fprintf(out, "ip link set \"$INTERFACE\" address %s\n", address_str); + break; + + case SUBNET_IPV4: + fprintf(out, "ip addr replace %s dev \"$INTERFACE\"\n", address_str); + break; + + case SUBNET_IPV6: + fprintf(out, "ip addr replace %s dev \"$INTERFACE\"\n", address_str); + break; + + default: + return; + } + +#elif defined(HAVE_MINGW) + + switch(address.type) { + case SUBNET_MAC: + fprintf(out, "ip link set \"$INTERFACE\" address %s\n", address_str); + break; + + case SUBNET_IPV4: + fprintf(out, "netsh interface ipv4 set address \"%%INTERFACE%%\" static %s\n", address_str); + break; + + case SUBNET_IPV6: + fprintf(out, "netsh interface ipv6 set address \"%%INTERFACE%%\" %s\n", address_str); + break; + + default: + return; + } + +#else // assume BSD + + switch(address.type) { + case SUBNET_MAC: + fprintf(out, "ifconfig \"$INTERFACE\" link %s\n", address_str); + break; + + case SUBNET_IPV4: + fprintf(out, "ifconfig \"$INTERFACE\" %s\n", address_str); + break; + + case SUBNET_IPV6: + fprintf(out, "ifconfig \"$INTERFACE\" inet6 %s\n", address_str); + break; + + default: + return; + } + +#endif +} + +void ifconfig_route(FILE *out, const char *value) { + subnet_t subnet = {0}, gateway = {0}; + char subnet_str[MAXNETSTR] = "", gateway_str[MAXNETSTR] = ""; + char *sep = strchr(value, ' '); + + if(sep) { + *sep++ = 0; + } + + if(!str2net(&subnet, value) || !net2str(subnet_str, sizeof(subnet_str), &subnet) || subnet.type == SUBNET_MAC) { + fprintf(stderr, "Could not parse subnet in Route statement\n"); + return; + } + + if(sep) { + if(!str2net(&gateway, sep) || !net2str(gateway_str, sizeof(gateway_str), &gateway) || gateway.type != subnet.type) { + fprintf(stderr, "Could not parse gateway in Route statement\n"); + return; + } + + char *slash = strchr(gateway_str, '/'); + + if(slash) { + *slash = 0; + } + } + +#if defined(HAVE_LINUX) + + if(*gateway_str) { + switch(subnet.type) { + case SUBNET_IPV4: + fprintf(out, "ip route add %s via %s dev \"$INTERFACE\" onlink\n", subnet_str, gateway_str); + break; + + case SUBNET_IPV6: + fprintf(out, "ip route add %s via %s dev \"$INTERFACE\" onlink\n", subnet_str, gateway_str); + break; + + default: + return; + } + } else { + switch(subnet.type) { + case SUBNET_IPV4: + fprintf(out, "ip route add %s dev \"$INTERFACE\"\n", subnet_str); + break; + + case SUBNET_IPV6: + fprintf(out, "ip route add %s dev \"$INTERFACE\"\n", subnet_str); + break; + + default: + return; + } + } + +#elif defined(HAVE_MINGW) + + if(*gateway_str) { + switch(subnet.type) { + case SUBNET_IPV4: + fprintf(out, "netsh interface ipv4 add route %s \"%%INTERFACE%%\" %s\n", subnet_str, gateway_str); + break; + + case SUBNET_IPV6: + fprintf(out, "netsh interface ipv6 add route %s \"%%INTERFACE%%\" %s\n", subnet_str, gateway_str); + break; + + default: + return; + } + } else { + switch(subnet.type) { + case SUBNET_IPV4: + fprintf(out, "netsh interface ipv4 add route %s \"%%INTERFACE%%\"\n", subnet_str); + break; + + case SUBNET_IPV6: + fprintf(out, "netsh interface ipv6 add route %s \"%%INTERFACE%%\"\n", subnet_str); + break; + + default: + return; + } + } + +#else // assume BSD + + if(!*gateway_str) { + switch(subnet.type) { + case SUBNET_IPV4: + if(!ipv4.type) { + fprintf(stderr, "Route requested but no Ifconfig\n"); + return; + } + + net2str(gateway_str, sizeof(gateway_str), &ipv4); + break; + + case SUBNET_IPV6: + if(!ipv6.type) { + fprintf(stderr, "Route requested but no Ifconfig\n"); + return; + } + + net2str(gateway_str, sizeof(gateway_str), &ipv6); + break; + + default: + return; + } + + char *slash = strchr(gateway_str, '/'); + + if(slash) { + *slash = 0; + } + } + + switch(subnet.type) { + case SUBNET_IPV4: + fprintf(out, "route add %s %s\n", subnet_str, gateway_str); + break; + + case SUBNET_IPV6: + fprintf(out, "route add -inet6 %s %s\n", subnet_str, gateway_str); + break; + + default: + return; + } + +#endif +} diff --git a/src/ifconfig.h b/src/ifconfig.h new file mode 100644 index 0000000..854780b --- /dev/null +++ b/src/ifconfig.h @@ -0,0 +1,31 @@ +#ifndef TINC_IFCONFIG_H +#define TINC_IFCONFIG_H + +/* + ifconfig.h -- header for ifconfig.c. + Copyright (C) 2016 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. +*/ + +extern void ifconfig_dhcp(FILE *out); +extern void ifconfig_dhcp6(FILE *out); +extern void ifconfig_slaac(FILE *out); +extern void ifconfig_address(FILE *out, const char *value); +extern void ifconfig_route(FILE *out, const char *value); +extern void ifconfig_header(FILE *out); +extern bool ifconfig_footer(FILE *out); + +#endif diff --git a/src/info.c b/src/info.c new file mode 100644 index 0000000..758c0d1 --- /dev/null +++ b/src/info.c @@ -0,0 +1,357 @@ +/* + info.c -- Show information about a node, subnet or address + Copyright (C) 2012-2017 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 "control_common.h" +#include "list.h" +#include "subnet.h" +#include "tincctl.h" +#include "info.h" +#include "utils.h" +#include "xalloc.h" + +void logger(int level, int priority, const char *format, ...) { + (void)level; + (void)priority; + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fputc('\n', stderr); +} + +char *strip_weight(char *netstr) { + int len = strlen(netstr); + + if(len >= 3 && !strcmp(netstr + len - 3, "#10")) { + netstr[len - 3] = 0; + } + + return netstr; +} + +static int info_node(int fd, const char *item) { + // Check the list of nodes + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_NODES, item); + + bool found = false; + char line[4096]; + + char node[4096]; + char id[4096]; + char from[4096]; + char to[4096]; + char subnet[4096]; + char host[4096]; + char port[4096]; + char via[4096]; + char nexthop[4096]; + int code, req, cipher, digest, maclength, compression, distance; + short int pmtu, minmtu, maxmtu; + unsigned int options; + union { + node_status_t bits; + uint32_t raw; + } status_union; + node_status_t status; + long int last_state_change; + int udp_ping_rtt; + uint64_t in_packets, in_bytes, out_packets, out_bytes; + + while(recvline(fd, line, sizeof(line))) { + int n = sscanf(line, "%d %d %4095s %4095s %4095s port %4095s %d %d %d %d %x %"PRIx32" %4095s %4095s %d %hd %hd %hd %ld %d %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, &code, &req, node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_union.raw, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change, &udp_ping_rtt, &in_packets, &in_bytes, &out_packets, &out_bytes); + + if(n == 2) { + break; + } + + if(n != 24) { + fprintf(stderr, "Unable to parse node dump from tincd.\n"); + return 1; + } + + if(!strcmp(node, item)) { + found = true; + break; + } + } + + if(!found) { + fprintf(stderr, "Unknown node %s.\n", item); + return 1; + } + + while(recvline(fd, line, sizeof(line))) { + if(sscanf(line, "%d %d %4095s", &code, &req, node) == 2) { + break; + } + } + + printf("Node: %s\n", item); + printf("Node ID: %s\n", id); + printf("Address: %s port %s\n", host, port); + + char timestr[32] = "never"; + time_t lsc_time = last_state_change; + + if(last_state_change) { + strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&lsc_time)); + } + + status = status_union.bits; + + if(status.reachable) { + printf("Online since: %s\n", timestr); + } else { + printf("Last seen: %s\n", timestr); + } + + printf("Status: "); + + if(status.validkey) { + printf(" validkey"); + } + + if(status.visited) { + printf(" visited"); + } + + if(status.reachable) { + printf(" reachable"); + } + + if(status.indirect) { + printf(" indirect"); + } + + if(status.sptps) { + printf(" sptps"); + } + + if(status.udp_confirmed) { + printf(" udp_confirmed"); + } + + printf("\n"); + + printf("Options: "); + + if(options & OPTION_INDIRECT) { + printf(" indirect"); + } + + if(options & OPTION_TCPONLY) { + printf(" tcponly"); + } + + if(options & OPTION_PMTU_DISCOVERY) { + printf(" pmtu_discovery"); + } + + if(options & OPTION_CLAMP_MSS) { + printf(" clamp_mss"); + } + + printf("\n"); + printf("Protocol: %d.%d\n", PROT_MAJOR, OPTION_VERSION(options)); + printf("Reachability: "); + + if(!strcmp(host, "MYSELF")) { + printf("can reach itself\n"); + } else if(!status.reachable) { + printf("unreachable\n"); + } else if(strcmp(via, item)) { + printf("indirectly via %s\n", via); + } else if(!status.validkey) { + printf("unknown\n"); + } else if(minmtu > 0) { + printf("directly with UDP\nPMTU: %d\n", pmtu); + + if(udp_ping_rtt != -1) { + printf("RTT: %d.%03d\n", udp_ping_rtt / 1000, udp_ping_rtt % 1000); + } + } else if(!strcmp(nexthop, item)) { + printf("directly with TCP\n"); + } else { + printf("none, forwarded via %s\n", nexthop); + } + + printf("RX: %"PRIu64" packets %"PRIu64" bytes\n", in_packets, in_bytes); + printf("TX: %"PRIu64" packets %"PRIu64" bytes\n", out_packets, out_bytes); + + // List edges + printf("Edges: "); + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_EDGES, item); + + while(recvline(fd, line, sizeof(line))) { + int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, from, to); + + if(n == 2) { + break; + } + + if(n != 4) { + fprintf(stderr, "Unable to parse edge dump from tincd.\n%s\n", line); + return 1; + } + + if(!strcmp(from, item)) { + printf(" %s", to); + } + } + + printf("\n"); + + // List subnets + printf("Subnets: "); + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item); + + while(recvline(fd, line, sizeof(line))) { + int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, subnet, from); + + if(n == 2) { + break; + } + + if(n != 4) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + + if(!strcmp(from, item)) { + printf(" %s", strip_weight(subnet)); + } + } + + printf("\n"); + + return 0; +} + +static int info_subnet(int fd, const char *item) { + subnet_t subnet, find; + + if(!str2net(&find, item)) { + fprintf(stderr, "Could not parse subnet or address '%s'.\n", item); + return 1; + } + + bool address = !strchr(item, '/'); + bool weight = strchr(item, '#'); + bool found = false; + + char line[4096]; + char netstr[4096]; + char owner[4096]; + + int code, req; + + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item); + + while(recvline(fd, line, sizeof(line))) { + int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, netstr, owner); + + if(n == 2) { + break; + } + + if(n != 4 || !str2net(&subnet, netstr)) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + + if(find.type != subnet.type) { + continue; + } + + if(weight) { + if(find.weight != subnet.weight) { + continue; + } + } + + if(find.type == SUBNET_IPV4) { + if(address) { + if(maskcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, subnet.net.ipv4.prefixlength)) { + continue; + } + } else { + if(find.net.ipv4.prefixlength != subnet.net.ipv4.prefixlength) { + continue; + } + + if(memcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, sizeof(subnet.net.ipv4))) { + continue; + } + } + } else if(find.type == SUBNET_IPV6) { + if(address) { + if(maskcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, subnet.net.ipv6.prefixlength)) { + continue; + } + } else { + if(find.net.ipv6.prefixlength != subnet.net.ipv6.prefixlength) { + continue; + } + + if(memcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, sizeof(subnet.net.ipv6))) { + continue; + } + } + } + + if(find.type == SUBNET_MAC) { + if(memcmp(&find.net.mac.address, &subnet.net.mac.address, sizeof(subnet.net.mac))) { + continue; + } + } + + found = true; + printf("Subnet: %s\n", strip_weight(netstr)); + printf("Owner: %s\n", owner); + } + + if(!found) { + if(address) { + fprintf(stderr, "Unknown address %s.\n", item); + } else { + fprintf(stderr, "Unknown subnet %s.\n", item); + } + + return 1; + } + + return 0; +} + +int info(int fd, const char *item) { + if(check_id(item)) { + return info_node(fd, item); + } + + if(strchr(item, '.') || strchr(item, ':')) { + return info_subnet(fd, item); + } + + fprintf(stderr, "Argument is not a node name, subnet or address.\n"); + return 1; +} diff --git a/src/info.h b/src/info.h new file mode 100644 index 0000000..1b323e0 --- /dev/null +++ b/src/info.h @@ -0,0 +1,26 @@ +#ifndef TINC_INFO_H +#define TINC_INFO_H + +/* + info.h -- header for info.c. + Copyright (C) 2012 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. +*/ + +extern int info(int fd, const char *item); +extern char *strip_weight(char *); + +#endif diff --git a/src/invitation.c b/src/invitation.c new file mode 100644 index 0000000..2976968 --- /dev/null +++ b/src/invitation.c @@ -0,0 +1,1364 @@ +/* + invitation.c -- Create and accept invitations + Copyright (C) 2013-2017 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 "control_common.h" +#include "crypto.h" +#include "ecdsa.h" +#include "ecdsagen.h" +#include "ifconfig.h" +#include "invitation.h" +#include "names.h" +#include "netutl.h" +#include "rsagen.h" +#include "script.h" +#include "sptps.h" +#include "subnet.h" +#include "tincctl.h" +#include "utils.h" +#include "xalloc.h" + +#include "ed25519/sha512.h" + +int addressfamily = AF_UNSPEC; + +static void scan_for_hostname(const char *filename, char **hostname, char **port) { + if(!filename || (*hostname && *port)) { + return; + } + + FILE *f = fopen(filename, "r"); + + if(!f) { + return; + } + + while(fgets(line, sizeof(line), f)) { + if(!rstrip(line)) { + continue; + } + + char *p = line, *q; + p += strcspn(p, "\t ="); + + if(!*p) { + continue; + } + + q = p + strspn(p, "\t "); + + if(*q == '=') { + q += 1 + strspn(q + 1, "\t "); + } + + *p = 0; + p = q + strcspn(q, "\t "); + + if(*p) { + *p++ = 0; + } + + p += strspn(p, "\t "); + p[strcspn(p, "\t ")] = 0; + + if(!*port && !strcasecmp(line, "Port")) { + *port = xstrdup(q); + } else if(!*hostname && !strcasecmp(line, "Address")) { + *hostname = xstrdup(q); + + if(*p) { + free(*port); + *port = xstrdup(p); + } + } + + if(*hostname && *port) { + break; + } + } + + fclose(f); +} + +char *get_my_hostname() { + char *hostname = NULL; + char *port = NULL; + char *hostport = NULL; + char *name = get_my_name(false); + char filename[PATH_MAX] = {0}; + + // Use first Address statement in own host config file + if(check_id(name)) { + snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, name); + scan_for_hostname(filename, &hostname, &port); + scan_for_hostname(tinc_conf, &hostname, &port); + } + + if(hostname) { + goto done; + } + + // If that doesn't work, guess externally visible hostname + fprintf(stderr, "Trying to discover externally visible hostname...\n"); + struct addrinfo *ai = str2addrinfo("tinc-vpn.org", "80", SOCK_STREAM); + struct addrinfo *aip = ai; + static const char request[] = "GET http://tinc-vpn.org/host.cgi HTTP/1.0\r\n\r\n"; + + while(aip) { + int s = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); + + if(s >= 0) { + if(connect(s, aip->ai_addr, aip->ai_addrlen)) { + closesocket(s); + s = -1; + } + } + + if(s >= 0) { + send(s, request, sizeof(request) - 1, 0); + int len = recv(s, line, sizeof(line) - 1, MSG_WAITALL); + + if(len > 0) { + line[len] = 0; + + if(line[len - 1] == '\n') { + line[--len] = 0; + } + + char *p = strrchr(line, '\n'); + + if(p && p[1]) { + hostname = xstrdup(p + 1); + } + } + + closesocket(s); + + if(hostname) { + break; + } + } + + aip = aip->ai_next; + continue; + } + + if(ai) { + freeaddrinfo(ai); + } + + // Check that the hostname is reasonable + if(hostname) { + for(char *p = hostname; *p; p++) { + if(isalnum(*p) || *p == '-' || *p == '.' || *p == ':') { + continue; + } + + // If not, forget it. + free(hostname); + hostname = NULL; + break; + } + } + + if(!tty) { + if(!hostname) { + fprintf(stderr, "Could not determine the external address or hostname. Please set Address manually.\n"); + free(port); + return NULL; + } + + goto save; + } + +again: + fprintf(stderr, "Please enter your host's external address or hostname"); + + if(hostname) { + fprintf(stderr, " [%s]", hostname); + } + + fprintf(stderr, ": "); + + if(!fgets(line, sizeof(line), stdin)) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + free(hostname); + free(port); + return NULL; + } + + if(!rstrip(line)) { + if(hostname) { + goto save; + } else { + goto again; + } + } + + for(char *p = line; *p; p++) { + if(isalnum(*p) || *p == '-' || *p == '.') { + continue; + } + + fprintf(stderr, "Invalid address or hostname.\n"); + goto again; + } + + free(hostname); + hostname = xstrdup(line); + +save: + + if(*filename) { + FILE *f = fopen(filename, "a"); + + if(f) { + fprintf(f, "\nAddress = %s\n", hostname); + fclose(f); + } else { + fprintf(stderr, "Could not append Address to %s: %s\n", filename, strerror(errno)); + } + } + +done: + + if(port) { + if(strchr(hostname, ':')) { + xasprintf(&hostport, "[%s]:%s", hostname, port); + } else { + xasprintf(&hostport, "%s:%s", hostname, port); + } + } else { + if(strchr(hostname, ':')) { + xasprintf(&hostport, "[%s]", hostname); + } else { + hostport = xstrdup(hostname); + } + } + + free(hostname); + free(port); + return hostport; +} + +static bool fcopy(FILE *out, const char *filename) { + FILE *in = fopen(filename, "r"); + + if(!in) { + fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + return false; + } + + char buf[1024]; + size_t len; + + while((len = fread(buf, 1, sizeof(buf), in))) { + fwrite(buf, len, 1, out); + } + + fclose(in); + return true; +} + +int cmd_invite(int argc, char *argv[]) { + if(argc < 2) { + fprintf(stderr, "Not enough arguments!\n"); + return 1; + } + + // Check validity of the new node's name + if(!check_id(argv[1])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + myname = get_my_name(true); + + if(!myname) { + return 1; + } + + // Ensure no host configuration file with that name exists + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, argv[1]); + + if(!access(filename, F_OK)) { + fprintf(stderr, "A host config file for %s already exists!\n", argv[1]); + return 1; + } + + // If a daemon is running, ensure no other nodes know about this name + if(connect_tincd(false)) { + bool found = false; + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + + while(recvline(fd, line, sizeof(line))) { + char node[4096]; + int code, req; + + if(sscanf(line, "%d %d %4095s", &code, &req, node) != 3) { + break; + } + + if(!strcmp(node, argv[1])) { + found = true; + } + } + + if(found) { + fprintf(stderr, "A node with name %s is already known!\n", argv[1]); + return 1; + } + } + + snprintf(filename, sizeof(filename), "%s" SLASH "invitations", confbase); + + if(mkdir(filename, 0700) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno)); + return 1; + } + + // Count the number of valid invitations, clean up old ones + DIR *dir = opendir(filename); + + if(!dir) { + fprintf(stderr, "Could not read directory %s: %s\n", filename, strerror(errno)); + return 1; + } + + errno = 0; + int count = 0; + struct dirent *ent; + time_t deadline = time(NULL) - 604800; // 1 week in the past + + while((ent = readdir(dir))) { + if(strlen(ent->d_name) != 24) { + continue; + } + + char invname[PATH_MAX]; + struct stat st; + + if((size_t)snprintf(invname, sizeof(invname), "%s" SLASH "%s", filename, ent->d_name) >= sizeof(invname)) { + fprintf(stderr, "Filename too long: %s" SLASH "%s\n", filename, ent->d_name); + continue; + } + + if(!stat(invname, &st)) { + if(deadline < st.st_mtime) { + count++; + } else { + unlink(invname); + } + } else { + fprintf(stderr, "Could not stat %s: %s\n", invname, strerror(errno)); + errno = 0; + } + } + + closedir(dir); + + if(errno) { + fprintf(stderr, "Error while reading directory %s: %s\n", filename, strerror(errno)); + return 1; + } + + ecdsa_t *key; + snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "ed25519_key.priv", confbase); + + // Remove the key if there are no outstanding invitations. + if(!count) { + unlink(filename); + } + + // Create a new key if necessary. + FILE *f = fopen(filename, "r"); + + if(!f) { + if(errno != ENOENT) { + fprintf(stderr, "Could not read %s: %s\n", filename, strerror(errno)); + return 1; + } + + key = ecdsa_generate(); + + if(!key) { + return 1; + } + + f = fopen(filename, "w"); + + if(!f) { + fprintf(stderr, "Could not write %s: %s\n", filename, strerror(errno)); + return 1; + } + + chmod(filename, 0600); + + if(!ecdsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Could not write ECDSA private key\n"); + fclose(f); + return 1; + } + + fclose(f); + + if(connect_tincd(true)) { + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + } else { + fprintf(stderr, "Could not signal the tinc daemon. Please restart or reload it manually.\n"); + } + } else { + key = ecdsa_read_pem_private_key(f); + fclose(f); + + if(!key) { + fprintf(stderr, "Could not read private key from %s\n", filename); + } + } + + if(!key) { + return 1; + } + + // Create a hash of the key. + char hash[64]; + char *fingerprint = ecdsa_get_base64_public_key(key); + sha512(fingerprint, strlen(fingerprint), hash); + b64encode_urlsafe(hash, hash, 18); + + // Create a random cookie for this invitation. + char cookie[25]; + randomize(cookie, 18); + + // Create a filename that doesn't reveal the cookie itself + char buf[18 + strlen(fingerprint)]; + char cookiehash[64]; + memcpy(buf, cookie, 18); + memcpy(buf + 18, fingerprint, sizeof(buf) - 18); + sha512(buf, sizeof(buf), cookiehash); + b64encode_urlsafe(cookiehash, cookiehash, 18); + + b64encode_urlsafe(cookie, cookie, 18); + + // Create a file containing the details of the invitation. + snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "%s", confbase, cookiehash); + int ifd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600); + + if(!ifd) { + fprintf(stderr, "Could not create invitation file %s: %s\n", filename, strerror(errno)); + return 1; + } + + f = fdopen(ifd, "w"); + + if(!f) { + abort(); + } + + // Get the local address + char *address = get_my_hostname(); + + // Fill in the details. + fprintf(f, "Name = %s\n", argv[1]); + + if(check_netname(netname, true)) { + fprintf(f, "NetName = %s\n", netname); + } + + fprintf(f, "ConnectTo = %s\n", myname); + + // Copy Broadcast and Mode + FILE *tc = fopen(tinc_conf, "r"); + + if(tc) { + char buf[1024]; + + while(fgets(buf, sizeof(buf), tc)) { + if((!strncasecmp(buf, "Mode", 4) && strchr(" \t=", buf[4])) + || (!strncasecmp(buf, "Broadcast", 9) && strchr(" \t=", buf[9]))) { + fputs(buf, f); + + // Make sure there is a newline character. + if(!strchr(buf, '\n')) { + fputc('\n', f); + } + } + } + + fclose(tc); + } + + fprintf(f, "#---------------------------------------------------------------#\n"); + fprintf(f, "Name = %s\n", myname); + + char filename2[PATH_MAX]; + snprintf(filename2, sizeof(filename2), "%s" SLASH "hosts" SLASH "%s", confbase, myname); + fcopy(f, filename2); + fclose(f); + + // Create an URL from the local address, key hash and cookie + char *url; + xasprintf(&url, "%s/%s%s", address, hash, cookie); + + // Call the inviation-created script + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", argv[1]); + environment_add(&env, "INVITATION_FILE=%s", filename); + environment_add(&env, "INVITATION_URL=%s", url); + execute_script("invitation-created", &env); + environment_exit(&env); + + puts(url); + free(url); + free(address); + + return 0; +} + +static int sock; +static char cookie[18]; +static sptps_t sptps; +static char *data; +static size_t datalen; +static bool success = false; + +static char cookie[18], hash[18]; + +static char *get_line(const char **data) { + if(!data || !*data) { + return NULL; + } + + if(! **data) { + *data = NULL; + return NULL; + } + + static char line[1024]; + const char *end = strchr(*data, '\n'); + size_t len = end ? (size_t)(end - *data) : strlen(*data); + + if(len >= sizeof(line)) { + fprintf(stderr, "Maximum line length exceeded!\n"); + return NULL; + } + + if(len && !isprint(**data)) { + abort(); + } + + memcpy(line, *data, len); + line[len] = 0; + + if(end) { + *data = end + 1; + } else { + *data = NULL; + } + + return line; +} + +static char *get_value(const char *data, const char *var) { + char *line = get_line(&data); + + if(!line) { + return NULL; + } + + char *sep = line + strcspn(line, " \t="); + char *val = sep + strspn(sep, " \t"); + + if(*val == '=') { + val += 1 + strspn(val + 1, " \t"); + } + + *sep = 0; + + if(strcasecmp(line, var)) { + return NULL; + } + + return val; +} + +static char *grep(const char *data, const char *var) { + static char value[1024]; + + const char *p = data; + int varlen = strlen(var); + + // Skip all lines not starting with var + while(strncasecmp(p, var, varlen) || !strchr(" \t=", p[varlen])) { + p = strchr(p, '\n'); + + if(!p) { + break; + } else { + p++; + } + } + + if(!p) { + return NULL; + } + + p += varlen; + p += strspn(p, " \t"); + + if(*p == '=') { + p += 1 + strspn(p + 1, " \t"); + } + + const char *e = strchr(p, '\n'); + + if(!e) { + return xstrdup(p); + } + + if((size_t)(e - p) >= sizeof(value)) { + fprintf(stderr, "Maximum line length exceeded!\n"); + return NULL; + } + + memcpy(value, p, e - p); + value[e - p] = 0; + return value; +} + +static bool finalize_join(void) { + const char *temp_name = get_value(data, "Name"); + + if(!temp_name) { + fprintf(stderr, "No Name found in invitation!\n"); + return false; + } + + size_t len = strlen(temp_name); + char name[len + 1]; + memcpy(name, temp_name, len); + name[len] = 0; + + if(!check_id(name)) { + fprintf(stderr, "Invalid Name found in invitation!\n"); + return false; + } + + if(!netname) { + netname = grep(data, "NetName"); + + if(netname && !check_netname(netname, true)) { + fprintf(stderr, "Unsafe NetName found in invitation!\n"); + return false; + } + } + + bool ask_netname = false; + char temp_netname[32]; + +make_names: + + if(!confbasegiven) { + free(confbase); + confbase = NULL; + } + + make_names(false); + + free(tinc_conf); + free(hosts_dir); + + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); + + if(!access(tinc_conf, F_OK)) { + fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); + + if(confbasegiven) { + return false; + } + + // Generate a random netname, ask for a better one later. + ask_netname = true; + snprintf(temp_netname, sizeof(temp_netname), "join_%x", rand()); + netname = temp_netname; + goto make_names; + } + + if(mkdir(confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno)); + return false; + } + + if(mkdir(hosts_dir, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); + return false; + } + + FILE *f = fopen(tinc_conf, "w"); + + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno)); + return false; + } + + fprintf(f, "Name = %s\n", name); + + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name); + FILE *fh = fopen(filename, "w"); + + if(!fh) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + fclose(f); + return false; + } + + snprintf(filename, sizeof(filename), "%s" SLASH "invitation-data", confbase); + FILE *finv = fopen(filename, "w"); + + if(!finv || fwrite(data, datalen, 1, finv) != 1) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + fclose(fh); + fclose(f); + fclose(finv); + return false; + } + + fclose(finv); + + snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up.invitation", confbase); + FILE *fup = fopen(filename, "w"); + + if(!fup) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + fclose(f); + fclose(fh); + return false; + } + + ifconfig_header(fup); + + // Filter first chunk on approved keywords, split between tinc.conf and hosts/Name + // Generate a tinc-up script from Ifconfig and Route keywords. + // Other chunks go unfiltered to their respective host config files + const char *p = data; + char *l, *value; + + while((l = get_line(&p))) { + // Ignore comments + if(*l == '#') { + continue; + } + + // Split line into variable and value + int len = strcspn(l, "\t ="); + value = l + len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + l[len] = 0; + + // Ignore lines with empty variable names + if(!*l) { + continue; + } + + // Is it a Name? + if(!strcasecmp(l, "Name")) { + if(strcmp(value, name)) { + break; + } else { + continue; + } + } else if(!strcasecmp(l, "NetName")) { + continue; + } + + // Check the list of known variables + bool found = false; + int i; + + for(i = 0; variables[i].name; i++) { + if(strcasecmp(l, variables[i].name)) { + continue; + } + + found = true; + break; + } + + // Handle Ifconfig and Route statements + if(!found) { + if(!strcasecmp(l, "Ifconfig")) { + if(!strcasecmp(value, "dhcp")) { + ifconfig_dhcp(fup); + } else if(!strcasecmp(value, "dhcp6")) { + ifconfig_dhcp6(fup); + } else if(!strcasecmp(value, "slaac")) { + ifconfig_slaac(fup); + } else { + ifconfig_address(fup, value); + } + + continue; + } else if(!strcasecmp(l, "Route")) { + ifconfig_route(fup, value); + continue; + } + } + + // Ignore unknown and unsafe variables + if(!found) { + fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l); + continue; + } else if(!(variables[i].type & VAR_SAFE)) { + if(force) { + fprintf(stderr, "Warning: unsafe variable '%s' in invitation.\n", l); + } else { + fprintf(stderr, "Ignoring unsafe variable '%s' in invitation.\n", l); + continue; + } + } + + // Copy the safe variable to the right config file + fprintf((variables[i].type & VAR_HOST) ? fh : f, "%s = %s\n", l, value); + } + + fclose(f); + bool valid_tinc_up = ifconfig_footer(fup); + fclose(fup); + + while(l && !strcasecmp(l, "Name")) { + if(!check_id(value)) { + fprintf(stderr, "Invalid Name found in invitation.\n"); + return false; + } + + if(!strcmp(value, name)) { + fprintf(stderr, "Secondary chunk would overwrite our own host config file.\n"); + return false; + } + + snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, value); + f = fopen(filename, "w"); + + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return false; + } + + while((l = get_line(&p))) { + if(!strcmp(l, "#---------------------------------------------------------------#")) { + continue; + } + + int len = strcspn(l, "\t ="); + + if(len == 4 && !strncasecmp(l, "Name", 4)) { + value = l + len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + l[len] = 0; + break; + } + + fputs(l, f); + fputc('\n', f); + } + + fclose(f); + } + + // Generate our key and send a copy to the server + ecdsa_t *key = ecdsa_generate(); + + if(!key) { + return false; + } + + char *b64key = ecdsa_get_base64_public_key(key); + + if(!b64key) { + return false; + } + + snprintf(filename, sizeof(filename), "%s" SLASH "ed25519_key.priv", confbase); + f = fopenmask(filename, "w", 0600); + + if(!f) { + return false; + } + + if(!ecdsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Error writing private key!\n"); + ecdsa_free(key); + fclose(f); + return false; + } + + fclose(f); + + fprintf(fh, "Ed25519PublicKey = %s\n", b64key); + + sptps_send_record(&sptps, 1, b64key, strlen(b64key)); + free(b64key); + ecdsa_free(key); + +#ifndef DISABLE_LEGACY + rsa_t *rsa = rsa_generate(2048, 0x1001); + snprintf(filename, sizeof(filename), "%s" SLASH "rsa_key.priv", confbase); + f = fopenmask(filename, "w", 0600); + + if(!f || !rsa_write_pem_private_key(rsa, f)) { + fprintf(stderr, "Could not write private RSA key\n"); + } else if(!rsa_write_pem_public_key(rsa, fh)) { + fprintf(stderr, "Could not write public RSA key\n"); + } + + fclose(f); + + fclose(fh); + + rsa_free(rsa); +#endif + + check_port(name); + +ask_netname: + + if(ask_netname && tty) { + fprintf(stderr, "Enter a new netname: "); + + if(!fgets(line, sizeof(line), stdin)) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + return false; + } + + if(!*line || *line == '\n') { + goto ask_netname; + } + + line[strlen(line) - 1] = 0; + + char newbase[PATH_MAX]; + + if((size_t)snprintf(newbase, sizeof(newbase), CONFDIR SLASH "tinc" SLASH "%s", line) >= sizeof(newbase)) { + fprintf(stderr, "Filename too long: " CONFDIR SLASH "tinc" SLASH "%s\n", line); + goto ask_netname; + } + + if(rename(confbase, newbase)) { + fprintf(stderr, "Error trying to rename %s to %s: %s\n", confbase, newbase, strerror(errno)); + goto ask_netname; + } + + netname = line; + make_names(false); + } + + char filename2[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up.invitation", confbase); + +#ifdef HAVE_MINGW + snprintf(filename2, sizeof(filename2), "%s" SLASH "tinc-up.bat", confbase); +#else + snprintf(filename2, sizeof(filename2), "%s" SLASH "tinc-up", confbase); +#endif + + if(valid_tinc_up) { + if(tty) { + FILE *fup = fopen(filename, "r"); + + if(fup) { + fprintf(stderr, "\nPlease review the following tinc-up script:\n\n"); + + char buf[MAXSIZE]; + + while(fgets(buf, sizeof(buf), fup)) { + fputs(buf, stderr); + } + + fclose(fup); + + int response = 0; + + do { + fprintf(stderr, "\nDo you want to use this script [y]es/[n]o/[e]dit? "); + response = tolower(getchar()); + } while(!strchr("yne", response)); + + fprintf(stderr, "\n"); + + if(response == 'e') { + char *command; +#ifndef HAVE_MINGW + const char *editor = getenv("VISUAL"); + + if(!editor) { + editor = getenv("EDITOR"); + } + + if(!editor) { + editor = "vi"; + } + + xasprintf(&command, "\"%s\" \"%s\"", editor, filename); +#else + xasprintf(&command, "edit \"%s\"", filename); +#endif + + if(system(command)) { + response = 'n'; + } else { + response = 'y'; + } + + free(command); + } + + if(response == 'y') { + rename(filename, filename2); + chmod(filename2, 0755); + fprintf(stderr, "tinc-up enabled.\n"); + } else { + fprintf(stderr, "tinc-up has been left disabled.\n"); + } + } + } else { + fprintf(stderr, "A tinc-up script was generated, but has been left disabled.\n"); + } + } else { + // A placeholder was generated. + rename(filename, filename2); + chmod(filename2, 0755); + } + + fprintf(stderr, "Configuration stored in: %s\n", confbase); + + return true; +} + + +static bool invitation_send(void *handle, uint8_t type, const void *vdata, size_t len) { + (void)handle; + (void)type; + const char *data = vdata; + + while(len) { + int result = send(sock, data, len, 0); + + if(result == -1 && errno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + data += result; + len -= result; + } + + return true; +} + +static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) { + (void)handle; + + switch(type) { + case SPTPS_HANDSHAKE: + return sptps_send_record(&sptps, 0, cookie, sizeof(cookie)); + + case 0: + data = xrealloc(data, datalen + len + 1); + memcpy(data + datalen, msg, len); + datalen += len; + data[datalen] = 0; + break; + + case 1: + return finalize_join(); + + case 2: + fprintf(stderr, "Invitation successfully accepted.\n"); + shutdown(sock, SHUT_RDWR); + success = true; + break; + + default: + return false; + } + + return true; +} + +int cmd_join(int argc, char *argv[]) { + free(data); + data = NULL; + datalen = 0; + + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + // Make sure confbase exists and is accessible. + if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno)); + return 1; + } + + if(mkdir(confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno)); + return 1; + } + + if(access(confbase, R_OK | W_OK | X_OK)) { + fprintf(stderr, "No permission to write in directory %s: %s\n", confbase, strerror(errno)); + return 1; + } + + // If a netname or explicit configuration directory is specified, check for an existing tinc.conf. + if((netname || confbasegiven) && !access(tinc_conf, F_OK)) { + fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); + return 1; + } + + // Either read the invitation from the command line or from stdin. + char *invitation; + + if(argc > 1) { + invitation = argv[1]; + } else { + if(tty) { + fprintf(stderr, "Enter invitation URL: "); + } + + errno = EPIPE; + + if(!fgets(line, sizeof(line), stdin)) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + return false; + } + + invitation = line; + } + + // Parse the invitation URL. + rstrip(line); + + char *slash = strchr(invitation, '/'); + + if(!slash) { + goto invalid; + } + + *slash++ = 0; + + if(strlen(slash) != 48) { + goto invalid; + } + + char *address = invitation; + char *port = NULL; + + if(*address == '[') { + address++; + char *bracket = strchr(address, ']'); + + if(!bracket) { + goto invalid; + } + + *bracket = 0; + + if(bracket[1] == ':') { + port = bracket + 2; + } + } else { + port = strchr(address, ':'); + + if(port) { + *port++ = 0; + } + } + + if(!port || !*port) { + port = "655"; + } + + if(!b64decode(slash, hash, 24) || !b64decode(slash + 24, cookie, 24)) { + goto invalid; + } + + // Generate a throw-away key for the invitation. + ecdsa_t *key = ecdsa_generate(); + + if(!key) { + return 1; + } + + char *b64key = ecdsa_get_base64_public_key(key); + + // Connect to the tinc daemon mentioned in the URL. + struct addrinfo *ai = str2addrinfo(address, port, SOCK_STREAM); + + if(!ai) { + return 1; + } + + struct addrinfo *aip = NULL; + +next: + if(!aip) { + aip = ai; + } else { + aip = aip->ai_next; + + if(!aip) { + freeaddrinfo(ai); + return 1; + } + } + + sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); + + if(sock <= 0) { + fprintf(stderr, "Could not open socket: %s\n", strerror(errno)); + goto next; + } + + if(connect(sock, aip->ai_addr, aip->ai_addrlen)) { + char *addrstr, *portstr; + sockaddr2str((sockaddr_t *)aip->ai_addr, &addrstr, &portstr); + fprintf(stderr, "Could not connect to %s port %s: %s\n", addrstr, portstr, strerror(errno)); + free(addrstr); + free(portstr); + closesocket(sock); + goto next; + } + + fprintf(stderr, "Connected to %s port %s...\n", address, port); + + // Tell him we have an invitation, and give him our throw-away key. + int len = snprintf(line, sizeof(line), "0 ?%s %d.%d\n", b64key, PROT_MAJOR, PROT_MINOR); + + if(len <= 0 || (size_t)len >= sizeof(line)) { + abort(); + } + + if(!sendline(sock, "0 ?%s %d.%d", b64key, PROT_MAJOR, 1)) { + fprintf(stderr, "Error sending request to %s port %s: %s\n", address, port, strerror(errno)); + closesocket(sock); + goto next; + } + + char hisname[4096] = ""; + int code, hismajor, hisminor = 0; + + if(!recvline(sock, line, sizeof(line)) || sscanf(line, "%d %4095s %d.%d", &code, hisname, &hismajor, &hisminor) < 3 || code != 0 || hismajor != PROT_MAJOR || !check_id(hisname) || !recvline(sock, line, sizeof(line)) || !rstrip(line) || sscanf(line, "%d ", &code) != 1 || code != ACK || strlen(line) < 3) { + fprintf(stderr, "Cannot read greeting from peer\n"); + closesocket(sock); + goto next; + } + + freeaddrinfo(ai); + + // Check if the hash of the key he gave us matches the hash in the URL. + char *fingerprint = line + 2; + char hishash[64]; + + if(sha512(fingerprint, strlen(fingerprint), hishash)) { + fprintf(stderr, "Could not create digest\n%s\n", line + 2); + return 1; + } + + if(memcmp(hishash, hash, 18)) { + fprintf(stderr, "Peer has an invalid key!\n%s\n", line + 2); + return 1; + + } + + ecdsa_t *hiskey = ecdsa_set_base64_public_key(fingerprint); + + if(!hiskey) { + return 1; + } + + // Start an SPTPS session + if(!sptps_start(&sptps, NULL, true, false, key, hiskey, "tinc invitation", 15, invitation_send, invitation_receive)) { + return 1; + } + + // Feed rest of input buffer to SPTPS + if(!sptps_receive_data(&sptps, buffer, blen)) { + return 1; + } + + while((len = recv(sock, line, sizeof(line), 0))) { + if(len < 0) { + if(errno == EINTR) { + continue; + } + + fprintf(stderr, "Error reading data from %s port %s: %s\n", address, port, strerror(errno)); + return 1; + } + + char *p = line; + + while(len) { + int done = sptps_receive_data(&sptps, p, len); + + if(!done) { + return 1; + } + + len -= done; + p += done; + } + } + + sptps_stop(&sptps); + ecdsa_free(hiskey); + ecdsa_free(key); + closesocket(sock); + + if(!success) { + fprintf(stderr, "Connection closed by peer, invitation cancelled.\n"); + return 1; + } + + return 0; + +invalid: + fprintf(stderr, "Invalid invitation URL.\n"); + return 1; +} diff --git a/src/invitation.h b/src/invitation.h new file mode 100644 index 0000000..6517fe8 --- /dev/null +++ b/src/invitation.h @@ -0,0 +1,26 @@ +#ifndef TINC_INVITATION_H +#define TINC_INVITATION_H + +/* + invitation.h -- header for invitation.c. + Copyright (C) 2013 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. +*/ + +int cmd_invite(int argc, char *argv[]); +int cmd_join(int argc, char *argv[]); + +#endif diff --git a/src/ipv4.h b/src/ipv4.h index 7979f7d..2761582 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -81,7 +81,7 @@ struct ip { uint8_t ip_p; uint16_t ip_sum; struct in_addr ip_src, ip_dst; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #endif #ifndef IP_OFFMASK @@ -143,7 +143,7 @@ struct icmp { #define icmp_radv icmp_dun.id_radv #define icmp_mask icmp_dun.id_mask #define icmp_data icmp_dun.id_data -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #endif #endif diff --git a/src/ipv6.h b/src/ipv6.h index 1642278..17bc586 100644 --- a/src/ipv6.h +++ b/src/ipv6.h @@ -4,7 +4,7 @@ /* ipv6.h -- missing IPv6 related definitions Copyright (C) 2005 Ivo Timmermans - 2006-2012 Guus Sliepen + 2006-2016 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 @@ -29,34 +29,11 @@ #define IPPROTO_ICMPV6 58 #endif -#ifndef HAVE_STRUCT_IN6_ADDR -struct in6_addr { - union { - uint8_t u6_addr8[16]; - uint16_t u6_addr16[8]; - uint32_t u6_addr32[4]; - } in6_u; -} __attribute__((__packed__)); -#define s6_addr in6_u.u6_addr8 -#define s6_addr16 in6_u.u6_addr16 -#define s6_addr32 in6_u.u6_addr32 -#endif - -#ifndef HAVE_STRUCT_SOCKADDR_IN6 -struct sockaddr_in6 { - uint16_t sin6_family; - uint16_t sin6_port; - uint32_t sin6_flowinfo; - struct in6_addr sin6_addr; - uint32_t sin6_scope_id; -} __attribute__((__packed__)); -#endif - #ifndef IN6_IS_ADDR_V4MAPPED #define IN6_IS_ADDR_V4MAPPED(a) \ - ((((const uint32_t *) (a))[0] == 0) \ - && (((const uint32_t *) (a))[1] == 0) \ - && (((const uint32_t *) (a))[2] == htonl (0xffff))) + ((((__const uint32_t *) (a))[0] == 0) \ + && (((__const uint32_t *) (a))[1] == 0) \ + && (((__const uint32_t *) (a))[2] == htonl (0xffff))) #endif #ifndef HAVE_STRUCT_IP6_HDR @@ -72,7 +49,7 @@ struct ip6_hdr { } ip6_ctlun; struct in6_addr ip6_src; struct in6_addr ip6_dst; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #define ip6_vfc ip6_ctlun.ip6_un2_vfc #define ip6_flow ip6_ctlun.ip6_un1.ip6_un1_flow #define ip6_plen ip6_ctlun.ip6_un1.ip6_un1_plen @@ -91,7 +68,7 @@ struct icmp6_hdr { uint16_t icmp6_un_data16[2]; uint8_t icmp6_un_data8[4]; } icmp6_dataun; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #define ICMP6_DST_UNREACH_NOROUTE 0 #define ICMP6_DST_UNREACH 1 #define ICMP6_PACKET_TOO_BIG 2 @@ -111,7 +88,7 @@ struct icmp6_hdr { struct nd_neighbor_solicit { struct icmp6_hdr nd_ns_hdr; struct in6_addr nd_ns_target; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #define ND_OPT_SOURCE_LINKADDR 1 #define ND_OPT_TARGET_LINKADDR 2 #define nd_ns_type nd_ns_hdr.icmp6_type @@ -124,7 +101,7 @@ struct nd_neighbor_solicit { struct nd_opt_hdr { uint8_t nd_opt_type; uint8_t nd_opt_len; -} __attribute__((__packed__)); +} __attribute__((__gcc_struct__)) __attribute((__packed__)); #endif #endif diff --git a/src/linux/device.c b/src/linux/device.c index 38debe8..94e223f 100644 --- a/src/linux/device.c +++ b/src/linux/device.c @@ -20,23 +20,20 @@ #include "../system.h" -#ifdef HAVE_LINUX_IF_TUN_H #include #define DEFAULT_DEVICE "/dev/net/tun" -#else -#define DEFAULT_DEVICE "/dev/tap0" -#endif #include "../conf.h" #include "../device.h" #include "../logger.h" +#include "../names.h" #include "../net.h" #include "../route.h" #include "../utils.h" #include "../xalloc.h" +#include "../device.h" typedef enum device_type_t { - DEVICE_TYPE_ETHERTAP, DEVICE_TYPE_TUN, DEVICE_TYPE_TAP, } device_type_t; @@ -49,30 +46,20 @@ static char *type = NULL; static char ifrname[IFNAMSIZ]; static const char *device_info; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - static bool setup_device(void) { - struct ifreq ifr; - bool t1q = false; - if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { device = xstrdup(DEFAULT_DEVICE); } if(!get_config_string(lookup_config(config_tree, "Interface"), &iface)) -#ifdef HAVE_LINUX_IF_TUN_H - if(netname != NULL) { + if(netname) { iface = xstrdup(netname); } -#else - iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device); -#endif device_fd = open(device, O_RDWR | O_NONBLOCK); if(device_fd < 0) { - logger(LOG_ERR, "Could not open %s: %s", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device, strerror(errno)); return false; } @@ -80,15 +67,12 @@ static bool setup_device(void) { fcntl(device_fd, F_SETFD, FD_CLOEXEC); #endif -#ifdef HAVE_LINUX_IF_TUN_H - /* Ok now check if this is an old ethertap or a new tun/tap thingie */ - - memset(&ifr, 0, sizeof(ifr)); + struct ifreq ifr = {0}; get_config_string(lookup_config(config_tree, "DeviceType"), &type); if(type && strcasecmp(type, "tun") && strcasecmp(type, "tap")) { - logger(LOG_ERR, "Unknown device type %s!", type); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown device type %s!", type); return false; } @@ -107,8 +91,10 @@ static bool setup_device(void) { } #ifdef IFF_ONE_QUEUE - /* Set IFF_ONE_QUEUE flag... */ + + bool t1q = false; + if(get_config_bool(lookup_config(config_tree, "IffOneQueue"), &t1q) && t1q) { ifr.ifr_flags |= IFF_ONE_QUEUE; } @@ -125,105 +111,93 @@ static bool setup_device(void) { ifrname[IFNAMSIZ - 1] = 0; free(iface); iface = xstrdup(ifrname); - } else if(errno == EPERM || errno == EBUSY) { - logger(LOG_ERR, "Error while trying to configure %s: %s", device, strerror(errno)); + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create a tun/tap interface from %s: %s", device, strerror(errno)); return false; - } else if(!ioctl(device_fd, (('T' << 8) | 202), &ifr)) { - logger(LOG_WARNING, "Old ioctl() request was needed for %s", device); - strncpy(ifrname, ifr.ifr_name, IFNAMSIZ); - ifrname[IFNAMSIZ - 1] = 0; - free(iface); - iface = xstrdup(ifrname); - } else -#endif - { - if(routing_mode == RMODE_ROUTER) { - overwrite_mac = true; + } + + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); + + if(ifr.ifr_flags & IFF_TAP) { + struct ifreq ifr_mac = {0}; + + if(!ioctl(device_fd, SIOCGIFHWADDR, &ifr_mac)) { + memcpy(mymac.x, ifr_mac.ifr_hwaddr.sa_data, ETH_ALEN); + } else { + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get MAC address of %s: %s", device, strerror(errno)); } - - device_info = "Linux ethertap device"; - device_type = DEVICE_TYPE_ETHERTAP; - free(iface); - iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device); } - if(overwrite_mac && !ioctl(device_fd, SIOCGIFHWADDR, &ifr)) { - memcpy(mymac.x, ifr.ifr_hwaddr.sa_data, ETH_ALEN); - } - - logger(LOG_INFO, "%s is a %s", device, device_info); - return true; } static void close_device(void) { close(device_fd); + device_fd = -1; free(type); + type = NULL; free(device); + device = NULL; free(iface); + iface = NULL; + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { - int lenin; + int inlen; switch(device_type) { case DEVICE_TYPE_TUN: - lenin = read(device_fd, packet->data + 10, MTU - 10); + inlen = read(device_fd, DATA(packet) + 10, MTU - 10); - if(lenin <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", + if(inlen <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); + + if(errno == EBADFD) { /* File descriptor in bad state */ + event_exit(); + } + return false; } - memset(packet->data, 0, 12); - packet->len = lenin + 10; + memset(DATA(packet), 0, 12); + packet->len = inlen + 10; break; case DEVICE_TYPE_TAP: - lenin = read(device_fd, packet->data, MTU); + inlen = read(device_fd, DATA(packet), MTU); - if(lenin <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", + if(inlen <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - packet->len = lenin; + packet->len = inlen; break; - case DEVICE_TYPE_ETHERTAP: - lenin = read(device_fd, packet->data - 2, MTU + 2); - - if(lenin <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", - device_info, device, strerror(errno)); - return false; - } - - packet->len = lenin - 2; - break; + default: + abort(); } - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, - device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); switch(device_type) { case DEVICE_TYPE_TUN: - packet->data[10] = packet->data[11] = 0; + DATA(packet)[10] = DATA(packet)[11] = 0; - if(write(device_fd, packet->data + 10, packet->len - 10) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + if(write(device_fd, DATA(packet) + 10, packet->len - 10) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -231,41 +205,24 @@ static bool write_packet(vpn_packet_t *packet) { break; case DEVICE_TYPE_TAP: - if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + if(write(device_fd, DATA(packet), packet->len) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } break; - case DEVICE_TYPE_ETHERTAP: - memcpy(packet->data - 2, &packet->len, 2); - - if(write(device_fd, packet->data - 2, packet->len + 2) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, - strerror(errno)); - return false; - } - - break; + default: + abort(); } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t os_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/list.c b/src/list.c index a807c6d..27494c8 100644 --- a/src/list.c +++ b/src/list.c @@ -1,7 +1,7 @@ /* list.c -- functions to deal with double linked lists Copyright (C) 2000-2005 Ivo Timmermans - 2000-2006 Guus Sliepen + 2000-2013 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 @@ -26,9 +26,7 @@ /* (De)constructors */ list_t *list_alloc(list_action_t delete) { - list_t *list; - - list = xmalloc_and_zero(sizeof(list_t)); + list_t *list = xzalloc(sizeof(list_t)); list->delete = delete; return list; @@ -39,7 +37,7 @@ void list_free(list_t *list) { } list_node_t *list_alloc_node(void) { - return xmalloc_and_zero(sizeof(list_node_t)); + return xzalloc(sizeof(list_node_t)); } void list_free_node(list_t *list, list_node_t *node) { @@ -53,9 +51,7 @@ void list_free_node(list_t *list, list_node_t *node) { /* Insertion and deletion */ list_node_t *list_insert_head(list_t *list, void *data) { - list_node_t *node; - - node = list_alloc_node(); + list_node_t *node = list_alloc_node(); node->data = data; node->prev = NULL; @@ -74,9 +70,7 @@ list_node_t *list_insert_head(list_t *list, void *data) { } list_node_t *list_insert_tail(list_t *list, void *data) { - list_node_t *node; - - node = list_alloc_node(); + list_node_t *node = list_alloc_node(); node->data = data; node->next = NULL; @@ -94,6 +88,46 @@ list_node_t *list_insert_tail(list_t *list, void *data) { return node; } +list_node_t *list_insert_after(list_t *list, list_node_t *after, void *data) { + list_node_t *node = list_alloc_node(); + + node->data = data; + node->next = after->next; + node->prev = after; + after->next = node; + + if(node->next) { + node->next->prev = node; + } else { + list->tail = node; + } + + list->count++; + + return node; +} + +list_node_t *list_insert_before(list_t *list, list_node_t *before, void *data) { + list_node_t *node; + + node = list_alloc_node(); + + node->data = data; + node->next = before; + node->prev = before->prev; + before->prev = node; + + if(node->prev) { + node->prev->next = node; + } else { + list->head = node; + } + + list->count++; + + return node; +} + void list_unlink_node(list_t *list, list_node_t *node) { if(node->prev) { node->prev->next = node->next; @@ -123,6 +157,13 @@ void list_delete_tail(list_t *list) { list_delete_node(list, list->tail); } +void list_delete(list_t *list, const void *data) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) + if(node->data == data) { + list_delete_node(list, node); + } +} + /* Head/tail lookup */ void *list_get_head(list_t *list) { @@ -144,10 +185,7 @@ void *list_get_tail(list_t *list) { /* Fast list deletion */ void list_delete_list(list_t *list) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { list_free_node(list, node); } @@ -157,22 +195,14 @@ void list_delete_list(list_t *list) { /* Traversing */ void list_foreach_node(list_t *list, list_action_node_t action) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) { action(node); } } void list_foreach(list_t *list, list_action_t action) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; - + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) if(node->data) { action(node->data); } - } } diff --git a/src/list.h b/src/list.h index b2a9b3d..3806495 100644 --- a/src/list.h +++ b/src/list.h @@ -4,7 +4,7 @@ /* list.h -- header file for list.c Copyright (C) 2000-2005 Ivo Timmermans - 2000-2006 Guus Sliepen + 2000-2012 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 @@ -30,8 +30,8 @@ typedef struct list_node_t { void *data; } list_node_t; -typedef void (*list_action_t)(const void *); -typedef void (*list_action_node_t)(const list_node_t *); +typedef void (*list_action_t)(const void *data); +typedef void (*list_action_node_t)(const list_node_t *node); typedef struct list_t { list_node_t *head; @@ -45,7 +45,7 @@ typedef struct list_t { /* (De)constructors */ -extern list_t *list_alloc(list_action_t) __attribute__((__malloc__)); +extern list_t *list_alloc(list_action_t delete) __attribute__((__malloc__)); extern void list_free(list_t *list); extern list_node_t *list_alloc_node(void); extern void list_free_node(list_t *list, list_node_t *node); @@ -54,6 +54,10 @@ extern void list_free_node(list_t *list, list_node_t *node); extern list_node_t *list_insert_head(list_t *list, void *data); extern list_node_t *list_insert_tail(list_t *list, void *data); +extern list_node_t *list_insert_after(list_t *list, list_node_t *node, void *data); +extern list_node_t *list_insert_before(list_t *list, list_node_t *node, void *data); + +extern void list_delete(list_t *list, const void *data); extern void list_unlink_node(list_t *list, list_node_t *node); extern void list_delete_node(list_t *list, list_node_t *node); @@ -75,4 +79,12 @@ extern void list_delete_list(list_t *list); extern void list_foreach(list_t *list, list_action_t action); extern void list_foreach_node(list_t *list, list_action_node_t action); +/* + Iterates over a list. + + CAUTION: while this construct supports deleting the current item, + it does *not* support deleting *other* nodes while iterating on the list. + */ +#define list_each(type, item, list) (type *item = (type *)1; item; item = NULL) for(list_node_t *node = (list)->head, *next; item = node ? node->data : NULL, next = node ? node->next : NULL, node; node = next) + #endif diff --git a/src/logger.c b/src/logger.c index 8d4aea1..062f759 100644 --- a/src/logger.c +++ b/src/logger.c @@ -1,6 +1,6 @@ /* logger.c -- logging code - Copyright (C) 2004-2016 Guus Sliepen + Copyright (C) 2004-2017 Guus Sliepen 2004-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,17 +21,146 @@ #include "system.h" #include "conf.h" +#include "meta.h" +#include "names.h" #include "logger.h" +#include "connection.h" +#include "control_common.h" +#include "process.h" +#include "sptps.h" -debug_t debug_level = DEBUG_NOTHING; +int debug_level = DEBUG_NOTHING; static logmode_t logmode = LOGMODE_STDERR; static pid_t logpid; -extern char *logfilename; static FILE *logfile = NULL; #ifdef HAVE_MINGW static HANDLE loghandle = NULL; #endif static const char *logident = NULL; +bool logcontrol = false; +int umbilical = 0; + +static void real_logger(int level, int priority, const char *message) { + char timestr[32] = ""; + static bool suppress = false; + + // Bail out early if there is nothing to do. + if(suppress) { + return; + } + + if(!logcontrol && (level > debug_level || logmode == LOGMODE_NULL)) { + return; + } + + if(level <= debug_level) { + switch(logmode) { + case LOGMODE_STDERR: + fprintf(stderr, "%s\n", message); + fflush(stderr); + break; + + case LOGMODE_FILE: + if(!now.tv_sec) { + gettimeofday(&now, NULL); + } + + time_t now_sec = now.tv_sec; + strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now_sec)); + fprintf(logfile, "%s %s[%ld]: %s\n", timestr, logident, (long)logpid, message); + fflush(logfile); + break; + + case LOGMODE_SYSLOG: +#ifdef HAVE_MINGW + { + const char *messages[] = {message}; + ReportEvent(loghandle, priority, 0, 0, NULL, 1, 0, messages, NULL); + } + +#else +#ifdef HAVE_SYSLOG_H + syslog(priority, "%s", message); +#endif +#endif + break; + + case LOGMODE_NULL: + break; + } + + if(umbilical && do_detach) { + write(umbilical, message, strlen(message)); + write(umbilical, "\n", 1); + } + } + + if(logcontrol) { + suppress = true; + logcontrol = false; + + for list_each(connection_t, c, connection_list) { + if(!c->status.log) { + continue; + } + + logcontrol = true; + + if(level > (c->outcompression >= 0 ? c->outcompression : debug_level)) { + continue; + } + + int len = strlen(message); + + if(send_request(c, "%d %d %d", CONTROL, REQ_LOG, len)) { + send_meta(c, message, len); + } + } + + suppress = false; + } +} + +void logger(int level, int priority, const char *format, ...) { + va_list ap; + char message[1024] = ""; + + va_start(ap, format); + int len = vsnprintf(message, sizeof(message), format, ap); + message[sizeof(message) - 1] = 0; + va_end(ap); + + if(len > 0 && (size_t)len < sizeof(message) - 1 && message[len - 1] == '\n') { + message[len - 1] = 0; + } + + real_logger(level, priority, message); +} + +static void sptps_logger(sptps_t *s, int s_errno, const char *format, va_list ap) { + (void)s_errno; + char message[1024]; + size_t msglen = sizeof(message); + + int len = vsnprintf(message, msglen, format, ap); + message[sizeof(message) - 1] = 0; + + if(len > 0 && (size_t)len < sizeof(message) - 1) { + if(message[len - 1] == '\n') { + message[--len] = 0; + } + + // WARNING: s->handle can point to a connection_t or a node_t, + // but both types have the name and hostname fields at the same offsets. + connection_t *c = s->handle; + + if(c) { + snprintf(message + len, sizeof(message) - len, " from %s (%s)", c->name, c->hostname); + } + } + + real_logger(DEBUG_ALWAYS, LOG_ERR, message); +} void openlogger(const char *ident, logmode_t mode) { logident = ident; @@ -58,7 +187,7 @@ void openlogger(const char *ident, logmode_t mode) { loghandle = RegisterEventSource(NULL, logident); if(!loghandle) { - fprintf(stderr, "Could not open log handle!"); + fprintf(stderr, "Could not open log handle!\n"); logmode = LOGMODE_NULL; } @@ -73,6 +202,12 @@ void openlogger(const char *ident, logmode_t mode) { case LOGMODE_NULL: break; } + + if(logmode != LOGMODE_NULL) { + sptps_log = sptps_logger; + } else { + sptps_log = sptps_log_quiet; + } } void reopenlogger() { @@ -84,7 +219,7 @@ void reopenlogger() { FILE *newfile = fopen(logfilename, "a"); if(!newfile) { - logger(LOG_ERR, "Unable to reopen log file %s: %s", logfilename, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to reopen log file %s: %s", logfilename, strerror(errno)); return; } @@ -92,60 +227,6 @@ void reopenlogger() { logfile = newfile; } -void logger(int priority, const char *format, ...) { - va_list ap; - char timestr[32] = ""; - time_t now; - - va_start(ap, format); - - switch(logmode) { - case LOGMODE_STDERR: - vfprintf(stderr, format, ap); - fprintf(stderr, "\n"); - fflush(stderr); - break; - - case LOGMODE_FILE: - now = time(NULL); - strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S", localtime(&now)); - fprintf(logfile, "%s %s[%ld]: ", timestr, logident, (long)logpid); - vfprintf(logfile, format, ap); - fprintf(logfile, "\n"); - fflush(logfile); - break; - - case LOGMODE_SYSLOG: -#ifdef HAVE_MINGW - { - char message[4096]; - const char *messages[] = {message}; - vsnprintf(message, sizeof(message), format, ap); - message[sizeof(message) - 1] = 0; - ReportEvent(loghandle, priority, 0, 0, NULL, 1, 0, messages, NULL); - } - -#else -#ifdef HAVE_SYSLOG_H -#ifdef HAVE_VSYSLOG - vsyslog(priority, format, ap); -#else - { - char message[4096]; - vsnprintf(message, sizeof(message), format, ap); - syslog(priority, "%s", message); - } -#endif - break; -#endif -#endif - - case LOGMODE_NULL: - break; - } - - va_end(ap); -} void closelogger(void) { switch(logmode) { diff --git a/src/logger.h b/src/logger.h index 5a17ffb..611fe38 100644 --- a/src/logger.h +++ b/src/logger.h @@ -3,7 +3,8 @@ /* logger.h -- header file for logger.c - Copyright (C) 2003-2016 Guus Sliepen + Copyright (C) 1998-2005 Ivo Timmermans + 2000-2017 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 @@ -21,16 +22,16 @@ */ typedef enum debug_t { - DEBUG_NOTHING = 0, /* Quiet mode, only show starting/stopping of the daemon */ + DEBUG_NOTHING = 0, /* Quiet mode, only show starting/stopping of the daemon */ DEBUG_ALWAYS = 0, - DEBUG_CONNECTIONS = 1, /* Show (dis)connects of other tinc daemons via TCP */ - DEBUG_ERROR = 2, /* Show error messages received from other hosts */ - DEBUG_STATUS = 2, /* Show status messages received from other hosts */ - DEBUG_PROTOCOL = 3, /* Show the requests that are sent/received */ - DEBUG_META = 4, /* Show contents of every request that is sent/received */ - DEBUG_TRAFFIC = 5, /* Show network traffic information */ - DEBUG_PACKET = 6, /* Show contents of each packet that is being sent/received */ - DEBUG_SCARY_THINGS = 10, /* You have been warned */ + DEBUG_CONNECTIONS = 1, /* Show (dis)connects of other tinc daemons via TCP */ + DEBUG_ERROR = 2, /* Show error messages received from other hosts */ + DEBUG_STATUS = 2, /* Show status messages received from other hosts */ + DEBUG_PROTOCOL = 3, /* Show the requests that are sent/received */ + DEBUG_META = 4, /* Show contents of every request that is sent/received */ + DEBUG_TRAFFIC = 5, /* Show network traffic information */ + DEBUG_PACKET = 6, /* Show contents of each packet that is being sent/received */ + DEBUG_SCARY_THINGS = 10 /* You have been warned */ } debug_t; typedef enum logmode_t { @@ -64,12 +65,14 @@ enum { #endif #endif -extern debug_t debug_level; +#include + +extern int debug_level; +extern bool logcontrol; +extern int umbilical; extern void openlogger(const char *ident, logmode_t mode); extern void reopenlogger(void); -extern void logger(int priority, const char *format, ...) __attribute__((__format__(printf, 2, 3))); +extern void logger(int level, int priority, const char *format, ...) __attribute__((__format__(printf, 3, 4))); extern void closelogger(void); -#define ifdebug(l) if(debug_level >= DEBUG_##l) - #endif diff --git a/src/meta.c b/src/meta.c index ee55ecd..4282a4e 100644 --- a/src/meta.c +++ b/src/meta.c @@ -1,6 +1,6 @@ /* meta.c -- handle the meta communication - Copyright (C) 2000-2017 Guus Sliepen , + Copyright (C) 2000-2018 Guus Sliepen , 2000-2005 Ivo Timmermans 2006 Scott Lamb @@ -21,125 +21,149 @@ #include "system.h" -#include -#include - -#include "avl_tree.h" +#include "cipher.h" #include "connection.h" #include "logger.h" #include "meta.h" #include "net.h" #include "protocol.h" -#include "proxy.h" #include "utils.h" #include "xalloc.h" -bool send_meta(connection_t *c, const char *buffer, int length) { - int outlen; - int result; +#ifndef MIN +static ssize_t MIN(ssize_t x, ssize_t y) { + return x < y ? x : y; +} +#endif - ifdebug(META) logger(LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length, - c->name, c->hostname); +bool send_meta_sptps(void *handle, uint8_t type, const void *buffer, size_t length) { + (void)type; + connection_t *c = handle; - if(!c->outbuflen) { - c->last_flushed_time = now; + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "send_meta_sptps() called with NULL pointer!"); + abort(); } - /* Find room in connection's buffer */ - if(length + c->outbuflen > c->outbufsize) { - c->outbufsize = length + c->outbuflen; - c->outbuf = xrealloc(c->outbuf, c->outbufsize); + buffer_add(&c->outbuf, buffer, length); + io_set(&c->io, IO_READ | IO_WRITE); + + return true; +} + +bool send_meta(connection_t *c, const char *buffer, size_t length) { + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!"); + abort(); } - if(length + c->outbuflen + c->outbufstart > c->outbufsize) { - memmove(c->outbuf, c->outbuf + c->outbufstart, c->outbuflen); - c->outbufstart = 0; + logger(DEBUG_META, LOG_DEBUG, "Sending %lu bytes of metadata to %s (%s)", (unsigned long)length, + c->name, c->hostname); + + if(c->protocol_minor >= 2) { + return sptps_send_record(&c->sptps, 0, buffer, length); } /* Add our data to buffer */ if(c->status.encryptout) { - /* Check encryption limits */ - if((uint64_t)length > c->outbudget) { - ifdebug(META) logger(LOG_ERR, "Byte limit exceeded for encryption to %s (%s)", c->name, c->hostname); +#ifdef DISABLE_LEGACY + return false; +#else + + if(length > c->outbudget) { + logger(DEBUG_META, LOG_ERR, "Byte limit exceeded for encryption to %s (%s)", c->name, c->hostname); return false; } else { c->outbudget -= length; } - result = EVP_EncryptUpdate(c->outctx, (unsigned char *)c->outbuf + c->outbufstart + c->outbuflen, - &outlen, (unsigned char *)buffer, length); + size_t outlen = length; - if(!result || outlen < length) { - logger(LOG_ERR, "Error while encrypting metadata to %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!cipher_encrypt(c->outcipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting metadata to %s (%s)", + c->name, c->hostname); return false; - } else if(outlen > length) { - logger(LOG_EMERG, "Encrypted data too long! Heap corrupted!"); - abort(); } - c->outbuflen += outlen; +#endif } else { - memcpy(c->outbuf + c->outbufstart + c->outbuflen, buffer, length); - c->outbuflen += length; + buffer_add(&c->outbuf, buffer, length); } + io_set(&c->io, IO_READ | IO_WRITE); + return true; } -bool flush_meta(connection_t *c) { - int result; - - ifdebug(META) logger(LOG_DEBUG, "Flushing %d bytes to %s (%s)", - c->outbuflen, c->name, c->hostname); - - while(c->outbuflen) { - result = send(c->socket, c->outbuf + c->outbufstart, c->outbuflen, 0); - - if(result <= 0) { - if(!errno || errno == EPIPE) { - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection closed by %s (%s)", - c->name, c->hostname); - } else if(errno == EINTR) { - continue; - } else if(sockwouldblock(sockerrno)) { - ifdebug(META) logger(LOG_DEBUG, "Flushing %d bytes to %s (%s) would block", - c->outbuflen, c->name, c->hostname); - return true; - } else { - logger(LOG_ERR, "Flushing meta data to %s (%s) failed: %s", c->name, - c->hostname, sockstrerror(sockerrno)); - } - - return false; - } - - c->outbufstart += result; - c->outbuflen -= result; +void send_meta_raw(connection_t *c, const char *buffer, size_t length) { + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!"); + abort(); } - c->outbufstart = 0; /* avoid unnecessary memmoves */ - return true; + logger(DEBUG_META, LOG_DEBUG, "Sending %lu bytes of raw metadata to %s (%s)", (unsigned long)length, + c->name, c->hostname); + + buffer_add(&c->outbuf, buffer, length); + + io_set(&c->io, IO_READ | IO_WRITE); } -void broadcast_meta(connection_t *from, const char *buffer, int length) { - avl_node_t *node; - connection_t *c; - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - - if(c != from && c->status.active) { +void broadcast_meta(connection_t *from, const char *buffer, size_t length) { + for list_each(connection_t, c, connection_list) + if(c != from && c->edge) { send_meta(c, buffer, length); } +} + +bool receive_meta_sptps(void *handle, uint8_t type, const void *vdata, uint16_t length) { + const char *data = vdata; + connection_t *c = handle; + + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "receive_meta_sptps() called with NULL pointer!"); + abort(); } + + if(type == SPTPS_HANDSHAKE) { + if(c->allow_request == ACK) { + return send_ack(c); + } else { + return true; + } + } + + if(!data) { + return true; + } + + /* Are we receiving a TCPpacket? */ + + if(c->tcplen) { + if(length != c->tcplen) { + return false; + } + + receive_tcppacket(c, data, length); + c->tcplen = 0; + return true; + } + + /* Change newline to null byte, just like non-SPTPS requests */ + + if(data[length - 1] == '\n') { + ((char *)data)[length - 1] = 0; + } + + /* Otherwise we are waiting for a request */ + + return receive_request(c, data); } bool receive_meta(connection_t *c) { - int oldlen, i, result; - int lenin, lenout, reqlen; - bool decrypted = false; + ssize_t inlen; char inbuf[MAXBUFSIZE]; + char *bufp = inbuf, *endp; /* Strategy: - Read as much as possible from the TCP socket in one go. @@ -150,109 +174,174 @@ bool receive_meta(connection_t *c) { - If not, keep stuff in buffer and exit. */ - lenin = recv(c->socket, c->buffer + c->buflen, MAXBUFSIZE - c->buflen, 0); + buffer_compact(&c->inbuf, MAXBUFSIZE); - if(lenin <= 0) { - if(!lenin || !errno) { - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection closed by %s (%s)", - c->name, c->hostname); + if(sizeof(inbuf) <= c->inbuf.len) { + logger(DEBUG_ALWAYS, LOG_ERR, "Input buffer full for %s (%s)", c->name, c->hostname); + return false; + } + + inlen = recv(c->socket, inbuf, sizeof(inbuf) - c->inbuf.len, 0); + + if(inlen <= 0) { + if(!inlen || !sockerrno) { + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", + c->name, c->hostname); } else if(sockwouldblock(sockerrno)) { return true; } else - logger(LOG_ERR, "Metadata socket read error for %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Metadata socket read error for %s (%s): %s", c->name, c->hostname, sockstrerror(sockerrno)); return false; } - oldlen = c->buflen; - c->buflen += lenin; + do { + /* Are we receiving a SPTPS packet? */ - while(lenin > 0) { - reqlen = 0; + if(c->sptpslen) { + ssize_t len = MIN(inlen, c->sptpslen - c->inbuf.len); + buffer_add(&c->inbuf, bufp, len); - /* Is it proxy metadata? */ + char *sptpspacket = buffer_read(&c->inbuf, c->sptpslen); - if(c->allow_request == PROXY) { - reqlen = receive_proxy_meta(c); + if(!sptpspacket) { + return true; + } - if(reqlen < 0) { + if(!receive_tcppacket_sptps(c, sptpspacket, c->sptpslen)) { return false; } - goto consume; + c->sptpslen = 0; + + bufp += len; + inlen -= len; + continue; } - /* Decrypt */ + if(c->protocol_minor >= 2) { + size_t len = sptps_receive_data(&c->sptps, bufp, inlen); - if(c->status.decryptin && !decrypted) { - /* Check decryption limits */ - if((uint64_t)lenin > c->inbudget) { - ifdebug(META) logger(LOG_ERR, "Byte limit exceeded for decryption from %s (%s)", c->name, c->hostname); + if(!len) { + return false; + } + + bufp += len; + inlen -= len; + continue; + } + + if(!c->status.decryptin) { + endp = memchr(bufp, '\n', inlen); + + if(endp) { + endp++; + } else { + endp = bufp + inlen; + } + + buffer_add(&c->inbuf, bufp, endp - bufp); + + inlen -= endp - bufp; + bufp = endp; + } else { +#ifdef DISABLE_LEGACY + return false; +#else + + if((size_t)inlen > c->inbudget) { + logger(DEBUG_META, LOG_ERR, "Byte limit exceeded for decryption from %s (%s)", c->name, c->hostname); return false; } else { - c->inbudget -= lenin; + c->inbudget -= inlen; } - result = EVP_DecryptUpdate(c->inctx, (unsigned char *)inbuf, &lenout, (unsigned char *)c->buffer + oldlen, lenin); + size_t outlen = inlen; - if(!result || lenout != lenin) { - logger(LOG_ERR, "Error while decrypting metadata from %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!cipher_decrypt(c->incipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || (size_t)inlen != outlen) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting metadata from %s (%s)", + c->name, c->hostname); return false; } - memcpy(c->buffer + oldlen, inbuf, lenin); - decrypted = true; + inlen = 0; +#endif } - /* Are we receiving a TCPpacket? */ + while(c->inbuf.len) { + /* Are we receiving a TCPpacket? */ - if(c->tcplen) { - if(c->tcplen <= c->buflen) { - if(c->allow_request != ALL) { - logger(LOG_ERR, "Got unauthorized TCP packet from %s (%s)", c->name, c->hostname); + if(c->tcplen) { + char *tcpbuffer = buffer_read(&c->inbuf, c->tcplen); + + if(!tcpbuffer) { + break; + } + + if(!c->node) { + if(c->outgoing && proxytype == PROXY_SOCKS4 && c->allow_request == ID) { + if(tcpbuffer[0] == 0 && tcpbuffer[1] == 0x5a) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected"); + return false; + } + } else if(c->outgoing && proxytype == PROXY_SOCKS5 && c->allow_request == ID) { + if(tcpbuffer[0] != 5) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server"); + return false; + } + + if(tcpbuffer[1] == (char)0xff) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected: unsuitable authentication method"); + return false; + } + + if(tcpbuffer[2] != 5) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Invalid response from proxy server"); + return false; + } + + if(tcpbuffer[3] == 0) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + } else { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request rejected"); + return false; + } + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "c->tcplen set but c->node is NULL!"); + abort(); + } + } else { + if(c->allow_request == ALL) { + receive_tcppacket(c, tcpbuffer, c->tcplen); + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Got unauthorized TCP packet from %s (%s)", c->name, c->hostname); + return false; + } + } + + c->tcplen = 0; + } + + /* Otherwise we are waiting for a request */ + + char *request = buffer_readline(&c->inbuf); + + if(request) { + bool result = receive_request(c, request); + + if(!result) { return false; } - receive_tcppacket(c, c->buffer, c->tcplen); - reqlen = c->tcplen; - c->tcplen = 0; - } - } else { - /* Otherwise we are waiting for a request */ - - for(i = oldlen; i < c->buflen; i++) { - if(c->buffer[i] == '\n') { - c->buffer[i] = '\0'; /* replace end-of-line by end-of-string so we can use sscanf */ - c->reqlen = reqlen = i + 1; - break; - } - } - - if(reqlen && !receive_request(c)) { - return false; + continue; + } else { + break; } } - -consume: - - if(reqlen) { - c->buflen -= reqlen; - lenin -= reqlen - oldlen; - memmove(c->buffer, c->buffer + reqlen, c->buflen); - oldlen = 0; - continue; - } else { - break; - } - } - - if(c->buflen >= MAXBUFSIZE) { - logger(LOG_ERR, "Metadata read buffer overflow for %s (%s)", - c->name, c->hostname); - return false; - } + } while(inlen); return true; } diff --git a/src/meta.h b/src/meta.h index 36914f1..dcf2419 100644 --- a/src/meta.h +++ b/src/meta.h @@ -3,7 +3,7 @@ /* meta.h -- header for meta.c - Copyright (C) 2000-2006 Guus Sliepen , + Copyright (C) 2000-2014 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -23,9 +23,11 @@ #include "connection.h" -extern bool send_meta(struct connection_t *c, const char *buffer, int length); -extern void broadcast_meta(struct connection_t *c, const char *buffer, int length); -extern bool flush_meta(struct connection_t *c); +extern bool send_meta(struct connection_t *c, const char *buffer, size_t length); +extern void send_meta_raw(struct connection_t *c, const char *buffer, size_t length); +extern bool send_meta_sptps(void *handle, uint8_t type, const void *data, size_t length); +extern bool receive_meta_sptps(void *handle, uint8_t type, const void *data, uint16_t length); +extern void broadcast_meta(struct connection_t *from, const char *buffer, size_t length); extern bool receive_meta(struct connection_t *c); #endif diff --git a/src/mingw/common.h b/src/mingw/common.h index 41b9dc5..ff052c9 100644 --- a/src/mingw/common.h +++ b/src/mingw/common.h @@ -1,3 +1,6 @@ +#ifndef TINC_MINGW_COMMON_H +#define TINC_MINGW_COMMON_H + /* * TAP-Win32 -- A kernel driver to provide virtual tap device functionality * on Windows. Originally derived from the CIPE-Win32 @@ -73,3 +76,5 @@ //========================================================= #define TAP_COMPONENT_ID "tap0801" + +#endif diff --git a/src/mingw/device.c b/src/mingw/device.c index 321c515..bf8ae13 100644 --- a/src/mingw/device.c +++ b/src/mingw/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction with Windows tap driver in a MinGW environment Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2016 Guus Sliepen + 2002-2018 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 @@ -26,6 +26,7 @@ #include "../conf.h" #include "../device.h" #include "../logger.h" +#include "../names.h" #include "../net.h" #include "../route.h" #include "../utils.h" @@ -35,67 +36,62 @@ int device_fd = -1; static HANDLE device_handle = INVALID_HANDLE_VALUE; +static io_t device_read_io; +static OVERLAPPED device_read_overlapped; +static OVERLAPPED device_write_overlapped; +static vpn_packet_t device_read_packet; +static vpn_packet_t device_write_packet; char *device = NULL; char *iface = NULL; static const char *device_info = "Windows tap device"; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - extern char *myport; -OVERLAPPED r_overlapped; -OVERLAPPED w_overlapped; -static DWORD WINAPI tapreader(void *bla) { +static void device_issue_read() { int status; - DWORD len; - vpn_packet_t packet; - int errors = 0; - - logger(LOG_DEBUG, "Tap reader running"); - - /* Read from tap device and send to parent */ - - r_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); for(;;) { - ResetEvent(r_overlapped.hEvent); + ResetEvent(device_read_overlapped.hEvent); - status = ReadFile(device_handle, packet.data, MTU, &len, &r_overlapped); + DWORD len; + status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped); if(!status) { - if(GetLastError() == ERROR_IO_PENDING) { - WaitForSingleObject(r_overlapped.hEvent, INFINITE); - - if(!GetOverlappedResult(device_handle, &r_overlapped, &len, FALSE)) { - continue; - } - } else { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + if(GetLastError() != ERROR_IO_PENDING) + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); - errors++; - if(errors >= 10) { - EnterCriticalSection(&mutex); - running = false; - LeaveCriticalSection(&mutex); - } - - usleep(1000000); - continue; - } + break; } - errors = 0; - packet.len = len; - packet.priority = 0; + device_read_packet.len = len; + device_read_packet.priority = 0; + route(myself, &device_read_packet); + } +} - EnterCriticalSection(&mutex); - route(myself, &packet); - LeaveCriticalSection(&mutex); +static void device_handle_read(void *data, int flags) { + (void)data; + (void)flags; + + DWORD len; + + if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, FALSE)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error getting read result from %s %s: %s", device_info, + device, strerror(errno)); + + if(GetLastError() != ERROR_IO_INCOMPLETE) { + /* Must reset event or it will keep firing. */ + ResetEvent(device_read_overlapped.hEvent); + } + + return; } - return 0; + device_read_packet.len = len; + device_read_packet.priority = 0; + route(myself, &device_read_packet); + device_issue_read(); } static bool setup_device(void) { @@ -107,24 +103,22 @@ static bool setup_device(void) { char adaptername[1024]; char tapname[1024]; DWORD len; - unsigned long status; bool found = false; int err; - HANDLE thread; get_config_string(lookup_config(config_tree, "Device"), &device); get_config_string(lookup_config(config_tree, "Interface"), &iface); if(device && iface) { - logger(LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected"); + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: both Device and Interface specified, results may not be as expected"); } /* Open registry and look for network adapters */ if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) { - logger(LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); return false; } @@ -182,7 +176,7 @@ static bool setup_device(void) { RegCloseKey(key); if(!found) { - logger(LOG_ERR, "No Windows tap device found!"); + logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!"); return false; } @@ -202,14 +196,34 @@ static bool setup_device(void) { } if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError())); return false; } + /* Get version information from tap device */ + + { + ULONG info[3] = {0}; + DWORD len; + + if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL)) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get version information from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); + } else { + logger(DEBUG_ALWAYS, LOG_INFO, "TAP-Windows driver version: %lu.%lu%s", info[0], info[1], info[2] ? " (DEBUG)" : ""); + + /* Warn if using >=9.21. This is because starting from 9.21, TAP-Win32 seems to use a different, less efficient write path. */ + if(info[0] == 9 && info[1] >= 21) + logger(DEBUG_ALWAYS, LOG_WARNING, + "You are using the newer (>= 9.0.0.21, NDIS6) series of TAP-Win32 drivers. " + "Using these drivers with tinc is not recommended as it can result in poor performance. " + "You might want to revert back to 9.0.0.9 instead."); + } + } + /* Get MAC address from tap device */ if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof(mymac.x), mymac.x, sizeof(mymac.x), &len, 0)) { - logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); return false; } @@ -217,106 +231,127 @@ static bool setup_device(void) { overwrite_mac = 1; } - /* Create overlapped events for tap I/O */ + device_info = "Windows tap device"; - r_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - w_overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); - /* Start the tap reader */ - - thread = CreateThread(NULL, 0, tapreader, NULL, 0, NULL); - - if(!thread) { - logger(LOG_ERR, "System call `%s' failed: %s", "CreateThread", winerror(GetLastError())); - return false; - } - - /* Set media status for newer TAP-Win32 devices */ - - status = true; - DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL); - - logger(LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + device_read_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + device_write_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); return true; } +static void enable_device(void) { + logger(DEBUG_ALWAYS, LOG_INFO, "Enabling %s", device_info); + + ULONG status = 1; + DWORD len; + DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL); + + /* We don't use the write event directly, but GetOverlappedResult() does, internally. */ + + io_add_event(&device_read_io, device_handle_read, NULL, device_read_overlapped.hEvent); + device_issue_read(); +} + +static void disable_device(void) { + logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info); + + io_del(&device_read_io); + + ULONG status = 0; + DWORD len; + DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL); + + /* Note that we don't try to cancel ongoing I/O here - we just stop listening. + This is because some TAP-Win32 drivers don't seem to handle cancellation very well, + especially when combined with other events such as the computer going to sleep - cases + were observed where the GetOverlappedResult() would just block indefinitely and never + return in that case. */ +} + static void close_device(void) { + CancelIo(device_handle); + + /* According to MSDN, CancelIo() does not necessarily wait for the operation to complete. + To prevent race conditions, make sure the operation is complete + before we close the event it's referencing. */ + + DWORD len; + + if(!GetOverlappedResult(device_handle, &device_read_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s read to cancel: %s", device_info, device, winerror(GetLastError())); + } + + if(device_write_packet.len > 0 && !GetOverlappedResult(device_handle, &device_write_overlapped, &len, TRUE) && GetLastError() != ERROR_OPERATION_ABORTED) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not wait for %s %s write to cancel: %s", device_info, device, winerror(GetLastError())); + } + + device_write_packet.len = 0; + + CloseHandle(device_read_overlapped.hEvent); + CloseHandle(device_write_overlapped.hEvent); + CloseHandle(device_handle); + device_handle = INVALID_HANDLE_VALUE; free(device); + device = NULL; free(iface); + iface = NULL; + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { + (void)packet; return false; } static bool write_packet(vpn_packet_t *packet) { - DWORD lenout; - static vpn_packet_t queue; + DWORD outlen; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); - /* Check if there is something in progress */ + if(device_write_packet.len > 0) { + /* Make sure the previous write operation is finished before we start the next one; + otherwise we end up with multiple write ops referencing the same OVERLAPPED structure, + which according to MSDN is a no-no. */ - if(queue.len) { - DWORD size; - BOOL success = GetOverlappedResult(device_handle, &w_overlapped, &size, FALSE); - - if(success) { - ResetEvent(&w_overlapped); - queue.len = 0; - } else { - int err = GetLastError(); - - if(err != ERROR_IO_INCOMPLETE) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error completing previously queued write: %s", winerror(err)); - ResetEvent(&w_overlapped); - queue.len = 0; + if(!GetOverlappedResult(device_handle, &device_write_overlapped, &outlen, FALSE)) { + if(GetLastError() != ERROR_IO_INCOMPLETE) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error completing previously queued write to %s %s: %s", device_info, device, winerror(GetLastError())); } else { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Previous overlapped write still in progress"); + logger(DEBUG_TRAFFIC, LOG_ERR, "Previous overlapped write to %s %s still in progress", device_info, device); // drop this packet return true; } } } - /* Otherwise, try to write. */ + /* Copy the packet, since the write operation might still be ongoing after we return. */ - memcpy(queue.data, packet->data, packet->len); + memcpy(&device_write_packet, packet, sizeof(*packet)); - if(!WriteFile(device_handle, queue.data, packet->len, &lenout, &w_overlapped)) { - int err = GetLastError(); + ResetEvent(device_write_overlapped.hEvent); - if(err != ERROR_IO_PENDING) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(err)); - return false; - } - - // Write is being done asynchronously. - queue.len = packet->len; - } else { + if(WriteFile(device_handle, DATA(&device_write_packet), device_write_packet.len, &outlen, &device_write_overlapped)) { // Write was completed immediately. - ResetEvent(&w_overlapped); + device_write_packet.len = 0; + } else if(GetLastError() != ERROR_IO_PENDING) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); + device_write_packet.len = 0; + return false; } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t os_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, + .enable = enable_device, + .disable = disable_device, }; diff --git a/src/multicast_device.c b/src/multicast_device.c index 93a40c4..e9607f0 100644 --- a/src/multicast_device.c +++ b/src/multicast_device.c @@ -1,7 +1,7 @@ /* device.c -- multicast socket Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2014 Guus Sliepen + 2002-2013 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 @@ -31,14 +31,11 @@ static const char *device_info = "multicast socket"; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - static struct addrinfo *ai = NULL; -static mac_t ignore_src = {{0}}; +static mac_t ignore_src = {0}; static bool setup_device(void) { - char *host; + char *host = NULL; char *port; char *space; int ttl = 1; @@ -46,17 +43,16 @@ static bool setup_device(void) { get_config_string(lookup_config(config_tree, "Interface"), &iface); if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { - logger(LOG_ERR, "Device variable required for %s", device_info); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Device variable required for %s", device_info); + goto error; } host = xstrdup(device); space = strchr(host, ' '); if(!space) { - logger(LOG_ERR, "Port number required for %s", device_info); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Port number required for %s", device_info); + goto error; } *space++ = 0; @@ -71,16 +67,14 @@ static bool setup_device(void) { ai = str2addrinfo(host, port, SOCK_DGRAM); if(!ai) { - free(host); - return false; + goto error; } device_fd = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP); if(device_fd < 0) { - logger(LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno)); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno)); + goto error; } #ifdef FD_CLOEXEC @@ -91,10 +85,8 @@ static bool setup_device(void) { setsockopt(device_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); if(bind(device_fd, ai->ai_addr, ai->ai_addrlen)) { - closesocket(device_fd); - logger(LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno)); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; } switch(ai->ai_family) { @@ -108,10 +100,8 @@ static bool setup_device(void) { mreq.imr_interface.s_addr = htonl(INADDR_ANY); if(setsockopt(device_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq))) { - logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); - closesocket(device_fd); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; } #ifdef IP_MULTICAST_LOOP @@ -134,10 +124,8 @@ static bool setup_device(void) { mreq.ipv6mr_interface = in6.sin6_scope_id; if(setsockopt(device_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void *)&mreq, sizeof(mreq))) { - logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); - closesocket(device_fd); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; } #ifdef IPV6_MULTICAST_LOOP @@ -151,97 +139,86 @@ static bool setup_device(void) { #endif default: - logger(LOG_ERR, "Multicast for address family %x unsupported", ai->ai_family); - closesocket(device_fd); - free(host); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %x unsupported", ai->ai_family); + goto error; } - free(host); - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; -} -static void close_device(void) { - close(device_fd); +error: - free(device); - free(iface); + if(device_fd >= 0) { + closesocket(device_fd); + } if(ai) { freeaddrinfo(ai); } + + free(host); + + return false; +} + +static void close_device(void) { + close(device_fd); + device_fd = -1; + + free(device); + device = NULL; + free(iface); + iface = NULL; + + if(ai) { + freeaddrinfo(ai); + ai = NULL; + } + + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { int lenin; - if((lenin = recv(device_fd, (void *)packet->data, MTU, 0)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, - device, strerror(errno)); + if((lenin = recv(device_fd, (void *)DATA(packet), MTU, 0)) <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, + device, sockstrerror(sockerrno)); return false; } - if(!memcmp(&ignore_src, packet->data + 6, sizeof(ignore_src))) { - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info); - packet->len = 0; - return true; + if(!memcmp(&ignore_src, DATA(packet) + 6, sizeof(ignore_src))) { + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info); + return false; } packet->len = lenin; - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, - device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); - if(sendto(device_fd, (void *)packet->data, packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, - strerror(errno)); + if(sendto(device_fd, (void *)DATA(packet), packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, + sockstrerror(sockerrno)); return false; } - device_total_out += packet->len; - - memcpy(&ignore_src, packet->data + 6, sizeof(ignore_src)); + memcpy(&ignore_src, DATA(packet) + 6, sizeof(ignore_src)); return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t multicast_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; - -#if 0 - -static bool not_supported(void) { - logger(LOG_ERR, "Raw socket device not supported on this platform"); - return false; -} - -const devops_t multicast_devops = { - .setup = not_supported, - .close = NULL, - .read = NULL, - .write = NULL, - .dump_stats = NULL, -}; -#endif diff --git a/src/names.c b/src/names.c new file mode 100644 index 0000000..603b536 --- /dev/null +++ b/src/names.c @@ -0,0 +1,181 @@ +/* + names.c -- generate commonly used (file)names + Copyright (C) 1998-2005 Ivo Timmermans + 2000-2018 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 "logger.h" +#include "names.h" +#include "xalloc.h" + +char *netname = NULL; +char *myname = NULL; +char *confdir = NULL; /* base configuration directory */ +char *confbase = NULL; /* base configuration directory for this instance of tinc */ +bool confbase_given; +char *identname = NULL; /* program name for syslog */ +char *unixsocketname = NULL; /* UNIX socket location */ +char *logfilename = NULL; /* log file location */ +char *pidfilename = NULL; +char *program_name = NULL; + +/* + Set all files and paths according to netname +*/ +void make_names(bool daemon) { +#ifdef HAVE_MINGW + HKEY key; + char installdir[1024] = ""; + DWORD len = sizeof(installdir); +#endif + confbase_given = confbase; + + if(netname && confbase) { + logger(DEBUG_ALWAYS, LOG_INFO, "Both netname and configuration directory given, using the latter..."); + } + + if(netname) { + xasprintf(&identname, "tinc.%s", netname); + } else { + identname = xstrdup("tinc"); + } + +#ifdef HAVE_MINGW + + if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\tinc", 0, KEY_READ, &key)) { + if(!RegQueryValueEx(key, NULL, 0, 0, (LPBYTE)installdir, &len)) { + confdir = xstrdup(installdir); + + if(!confbase) { + if(netname) { + xasprintf(&confbase, "%s" SLASH "%s", installdir, netname); + } else { + xasprintf(&confbase, "%s", installdir); + } + } + + if(!logfilename) { + xasprintf(&logfilename, "%s" SLASH "tinc.log", confbase); + } + } + + RegCloseKey(key); + } + +#endif + + if(!confdir) { + confdir = xstrdup(CONFDIR SLASH "tinc"); + } + + if(!confbase) { + if(netname) { + xasprintf(&confbase, CONFDIR SLASH "tinc" SLASH "%s", netname); + } else { + xasprintf(&confbase, CONFDIR SLASH "tinc"); + } + } + +#ifdef HAVE_MINGW + (void)daemon; + + if(!logfilename) { + xasprintf(&logfilename, "%s" SLASH "log", confbase); + } + + if(!pidfilename) { + xasprintf(&pidfilename, "%s" SLASH "pid", confbase); + } + +#else + bool fallback = false; + + if(daemon) { + if(access(LOCALSTATEDIR, R_OK | W_OK | X_OK)) { + fallback = true; + } + } else { + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), LOCALSTATEDIR SLASH "run" SLASH "%s.pid", identname); + + if(access(fname, R_OK)) { + snprintf(fname, sizeof(fname), "%s" SLASH "pid", confbase); + + if(!access(fname, R_OK)) { + fallback = true; + } + } + } + + if(!fallback) { + if(!logfilename) { + xasprintf(&logfilename, LOCALSTATEDIR SLASH "log" SLASH "%s.log", identname); + } + + if(!pidfilename) { + xasprintf(&pidfilename, LOCALSTATEDIR SLASH "run" SLASH "%s.pid", identname); + } + } else { + if(!logfilename) { + xasprintf(&logfilename, "%s" SLASH "log", confbase); + } + + if(!pidfilename) { + if(daemon) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not access " LOCALSTATEDIR SLASH " (%s), storing pid and socket files in %s" SLASH, strerror(errno), confbase); + } + + xasprintf(&pidfilename, "%s" SLASH "pid", confbase); + } + } + +#endif + + if(!unixsocketname) { + int len = strlen(pidfilename); + unixsocketname = xmalloc(len + 8); + memcpy(unixsocketname, pidfilename, len); + + if(len > 4 && !strcmp(pidfilename + len - 4, ".pid")) { + strncpy(unixsocketname + len - 4, ".socket", 8); + } else { + strncpy(unixsocketname + len, ".socket", 8); + } + } +} + +void free_names(void) { + free(identname); + free(netname); + free(unixsocketname); + free(pidfilename); + free(logfilename); + free(confbase); + free(confdir); + free(myname); + + identname = NULL; + netname = NULL; + unixsocketname = NULL; + pidfilename = NULL; + logfilename = NULL; + confbase = NULL; + confdir = NULL; + myname = NULL; +} diff --git a/src/names.h b/src/names.h new file mode 100644 index 0000000..f109686 --- /dev/null +++ b/src/names.h @@ -0,0 +1,38 @@ +#ifndef TINC_NAMES_H +#define TINC_NAMES_H + +/* + names.h -- header for names.c + Copyright (C) 1998-2005 Ivo Timmermans + 2000-2017 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. +*/ + +extern char *confdir; +extern char *confbase; +extern bool confbase_given; +extern char *netname; +extern char *myname; +extern char *identname; +extern char *unixsocketname; +extern char *logfilename; +extern char *pidfilename; +extern char *program_name; + +extern void make_names(bool daemon); +extern void free_names(void); + +#endif diff --git a/src/net.c b/src/net.c index 37ae116..ab4b635 100644 --- a/src/net.c +++ b/src/net.c @@ -1,7 +1,7 @@ /* net.c -- most of the network code Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2015 Guus Sliepen + 2000-2021 Guus Sliepen 2006 Scott Lamb 2011 Loïc Grenié @@ -22,59 +22,41 @@ #include "system.h" -#include - -#include "utils.h" -#include "avl_tree.h" +#include "autoconnect.h" #include "conf.h" #include "connection.h" #include "device.h" -#include "event.h" #include "graph.h" #include "logger.h" #include "meta.h" +#include "names.h" #include "net.h" #include "netutl.h" -#include "process.h" #include "protocol.h" -#include "route.h" #include "subnet.h" +#include "utils.h" #include "xalloc.h" -bool do_purge = false; -volatile bool running = false; -#ifdef HAVE_PSELECT -bool graph_dump = false; -#endif - -time_t now = 0; int contradicting_add_edge = 0; int contradicting_del_edge = 0; static int sleeptime = 10; +time_t last_config_check = 0; +static timeout_t pingtimer; +static timeout_t periodictimer; +static struct timeval last_periodic_run_time; /* Purge edges and subnets of unreachable nodes. Use carefully. */ -static void purge(void) { - avl_node_t *nnode, *nnext, *enode, *enext, *snode, *snext; - node_t *n; - edge_t *e; - subnet_t *s; - - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Purging unreachable nodes"); +void purge(void) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Purging unreachable nodes"); /* Remove all edges and subnets owned by unreachable nodes. */ - for(nnode = node_tree->head; nnode; nnode = nnext) { - nnext = nnode->next; - n = nnode->data; - + for splay_each(node_t, n, node_tree) { if(!n->status.reachable) { - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Purging node %s (%s)", n->name, - n->hostname); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Purging node %s (%s)", n->name, n->hostname); - for(snode = n->subnet_tree->head; snode; snode = snext) { - snext = snode->next; - s = snode->data; + for splay_each(subnet_t, s, n->subnet_tree) { send_del_subnet(everyone, s); if(!strictsubnets) { @@ -82,10 +64,7 @@ static void purge(void) { } } - for(enode = n->edge_tree->head; enode; enode = enext) { - enext = enode->next; - e = enode->data; - + for splay_each(edge_t, e, n->edge_tree) { if(!tunnelserver) { send_del_edge(everyone, e); } @@ -97,21 +76,14 @@ static void purge(void) { /* Check if anyone else claims to have an edge to an unreachable node. If not, delete node. */ - for(nnode = node_tree->head; nnode; nnode = nnext) { - nnext = nnode->next; - n = nnode->data; - + for splay_each(node_t, n, node_tree) { if(!n->status.reachable) { - for(enode = edge_weight_tree->head; enode; enode = enext) { - enext = enode->next; - e = enode->data; - + for splay_each(edge_t, e, edge_weight_tree) if(e->to == n) { - break; + return; } - } - if(!enode && (!strictsubnets || !n->subnet_tree->head)) + if(!autoconnect && (!strictsubnets || !n->subnet_tree->head)) /* in strictsubnets mode do not delete nodes with subnets */ { node_del(n); @@ -120,70 +92,10 @@ static void purge(void) { } } -/* - put all file descriptors in an fd_set array - While we're at it, purge stuff that needs to be removed. -*/ -static int build_fdset(fd_set *readset, fd_set *writeset) { - avl_node_t *node, *next; - connection_t *c; - int i, max = 0; - - FD_ZERO(readset); - FD_ZERO(writeset); - - for(node = connection_tree->head; node; node = next) { - next = node->next; - c = node->data; - - if(c->status.remove) { - connection_del(c); - - if(!connection_tree->head) { - purge(); - } - } else { - FD_SET(c->socket, readset); - - if(c->outbuflen > 0 || c->status.connecting) { - FD_SET(c->socket, writeset); - } - - if(c->socket > max) { - max = c->socket; - } - } - } - - for(i = 0; i < listen_sockets; i++) { - FD_SET(listen_socket[i].tcp, readset); - - if(listen_socket[i].tcp > max) { - max = listen_socket[i].tcp; - } - - FD_SET(listen_socket[i].udp, readset); - - if(listen_socket[i].udp > max) { - max = listen_socket[i].udp; - } - } - - if(device_fd >= 0) { - FD_SET(device_fd, readset); - } - - if(device_fd > max) { - max = device_fd; - } - - return max; -} - /* Put a misbehaving connection in the tarpit */ void tarpit(int fd) { static int pits[10] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; - static int next_pit = 0; + static unsigned int next_pit = 0; if(pits[next_pit] != -1) { closesocket(pits[next_pit]); @@ -191,82 +103,62 @@ void tarpit(int fd) { pits[next_pit++] = fd; - if(next_pit >= (int)(sizeof pits / sizeof pits[0])) { + if(next_pit >= sizeof pits / sizeof pits[0]) { next_pit = 0; } } /* Terminate a connection: - - Close the socket - - Remove associated edge and tell other connections about it if report = true + - Mark it as inactive + - Remove the edge representing this connection + - Kill it with fire - Check if we need to retry making an outgoing connection - - Deactivate the host */ void terminate_connection(connection_t *c, bool report) { - if(c->status.remove) { - return; - } - - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Closing connection with %s (%s)", - c->name, c->hostname); - - c->status.remove = true; - c->status.active = false; + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Closing connection with %s (%s)", c->name, c->hostname); if(c->node) { - c->node->connection = NULL; - } - - if(c->socket) { - if(c->status.tarpit) { - tarpit(c->socket); - } else { - closesocket(c->socket); - } - } - - if(c->edge) { - if(!c->node) { - logger(LOG_ERR, "Connection to %s (%s) has an edge but node is NULL!", c->name, c->hostname); - // And that should never happen. - abort(); + if(c->node->connection == c) { + c->node->connection = NULL; } - if(report && !tunnelserver) { - send_del_edge(everyone, c->edge); - } + if(c->edge) { + if(report && !tunnelserver) { + send_del_edge(everyone, c->edge); + } - edge_del(c->edge); - c->edge = NULL; + edge_del(c->edge); + c->edge = NULL; - /* Run MST and SSSP algorithms */ + /* Run MST and SSSP algorithms */ - graph(); + graph(); - /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */ + /* If the node is not reachable anymore but we remember it had an edge to us, clean it up */ - if(report && !c->node->status.reachable) { - edge_t *e; - e = lookup_edge(c->node, myself); + if(report && !c->node->status.reachable) { + edge_t *e; + e = lookup_edge(c->node, myself); - if(e) { - if(!tunnelserver) { - send_del_edge(everyone, e); + if(e) { + if(!tunnelserver) { + send_del_edge(everyone, e); + } + + edge_del(e); } - - edge_del(e); } } } - free_connection_partially(c); + outgoing_t *outgoing = c->outgoing; + connection_del(c); /* Check if this was our outgoing connection */ - if(c->outgoing) { - c->status.remove = false; - do_outgoing_connection(c); + if(outgoing) { + do_outgoing_connection(outgoing); } #ifndef HAVE_MINGW @@ -285,427 +177,349 @@ void terminate_connection(connection_t *c, bool report) { end does not reply in time, we consider them dead and close the connection. */ -static void check_dead_connections(void) { - avl_node_t *node, *next; - connection_t *c; +static void timeout_handler(void *data) { - for(node = connection_tree->head; node; node = next) { - next = node->next; - c = node->data; + bool close_all_connections = false; - if(c->last_ping_time + pingtimeout <= now) { - if(c->status.active) { - if(c->status.pinged) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds", - c->name, c->hostname, (long)(now - c->last_ping_time)); - c->status.timeout = true; - terminate_connection(c, true); - } else if(c->last_ping_time + pinginterval <= now) { - send_ping(c); - } - } else { - if(c->status.remove) { - logger(LOG_WARNING, "Old connection_t for %s (%s) status %04x still lingering, deleting...", - c->name, c->hostname, bitfield_to_int(&c->status, sizeof(c->status))); - connection_del(c); - continue; - } + /* + timeout_handler will start after 30 seconds from start of tincd + hold information about the elapsed time since last time the handler + has been run + */ + long sleep_time = now.tv_sec - last_periodic_run_time.tv_sec; - ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication", - c->name, c->hostname); - - if(c->status.connecting) { - c->status.connecting = false; - closesocket(c->socket); - do_outgoing_connection(c); - } else { - c->status.tarpit = true; - terminate_connection(c, false); - } - } - } - - if(c->outbuflen > 0 && c->last_flushed_time + pingtimeout <= now) { - if(c->status.active) { - ifdebug(CONNECTIONS) logger(LOG_INFO, - "%s (%s) could not flush for %ld seconds (%d bytes remaining)", - c->name, c->hostname, (long)(now - c->last_flushed_time), c->outbuflen); - c->status.timeout = true; - terminate_connection(c, true); - } - } - } -} - -/* - check all connections to see if anything - happened on their sockets -*/ -static void check_network_activity(fd_set *readset, fd_set *writeset) { - connection_t *c; - avl_node_t *node; - int result, i; - socklen_t len = sizeof(result); - vpn_packet_t packet; - static int errors = 0; - - /* check input from kernel */ - if(device_fd >= 0 && FD_ISSET(device_fd, readset)) { - if(devops.read(&packet)) { - if(packet.len) { - errors = 0; - packet.priority = 0; - route(myself, &packet); - } - } else { - usleep(errors * 50000); - errors++; - - if(errors > 10) { - logger(LOG_ERR, "Too many errors from %s, exiting!", device); - running = false; - } - } + /* + It seems that finding sane default value is harder than expected + Since we send every second a UDP packet to make holepunching work + And default UDP state expire on firewalls is between 15-30 seconds + we drop all connections after 60 Seconds - UDPDiscoveryTimeout=30 + by default + */ + if(sleep_time > 2 * udp_discovery_timeout) { + logger(DEBUG_ALWAYS, LOG_ERR, "Awaking from dead after %ld seconds of sleep", sleep_time); + /* + Do not send any packets to tinc after we wake up. + The other node probably closed our connection but we still + are holding context information to them. This may happen on + laptops or any other hardware which can be suspended for some time. + Sending any data to node that wasn't expecting it will produce + annoying and misleading errors on the other side about failed signature + verification and or about missing sptps context + */ + close_all_connections = true; } - /* check meta connections */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + last_periodic_run_time = now; - if(c->status.remove) { + for list_each(connection_t, c, connection_list) { + // control connections (eg. tinc ctl) do not have any timeout + if(c->status.control) { continue; } - if(FD_ISSET(c->socket, writeset)) { - if(c->status.connecting) { - c->status.connecting = false; - getsockopt(c->socket, SOL_SOCKET, SO_ERROR, (void *)&result, &len); + if(close_all_connections) { + logger(DEBUG_ALWAYS, LOG_ERR, "Forcing connection close after sleep time %s (%s)", c->name, c->hostname); + terminate_connection(c, c->edge); + continue; + } - if(!result) { - finish_connecting(c); + // Bail out early if we haven't reached the ping timeout for this node yet + if(c->last_ping_time + pingtimeout > now.tv_sec) { + continue; + } + + // timeout during connection establishing + if(!c->edge) { + if(c->status.connecting) { + logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname); + } else { + logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname); + c->status.tarpit = true; + } + + terminate_connection(c, c->edge); + continue; + } + + // helps in UDP holepunching + try_tx(c->node, false); + + // timeout during ping + if(c->status.pinged) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds", c->name, c->hostname, (long)(now.tv_sec - c->last_ping_time)); + terminate_connection(c, c->edge); + continue; + } + + // check whether we need to send a new ping + if(c->last_ping_time + pinginterval <= now.tv_sec) { + send_ping(c); + } + } + + timeout_set(data, &(struct timeval) { + 1, rand() % 100000 + }); +} + +static void periodic_handler(void *data) { + /* Check if there are too many contradicting ADD_EDGE and DEL_EDGE messages. + This usually only happens when another node has the same Name as this node. + If so, sleep for a short while to prevent a storm of contradicting messages. + */ + + if(contradicting_del_edge > 100 && contradicting_add_edge > 100) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime); + nanosleep(&(struct timespec) { + sleeptime, 0 + }, NULL); + sleeptime *= 2; + + if(sleeptime < 0) { + sleeptime = 3600; + } + } else { + sleeptime /= 2; + + if(sleeptime < 10) { + sleeptime = 10; + } + } + + contradicting_add_edge = 0; + contradicting_del_edge = 0; + + /* If AutoConnect is set, check if we need to make or break connections. */ + + if(autoconnect && node_tree->count > 1) { + do_autoconnect(); + } + + timeout_set(data, &(struct timeval) { + 5, rand() % 100000 + }); +} + +void handle_meta_connection_data(connection_t *c) { + if(!receive_meta(c)) { + if(!c->status.control) { + c->status.tarpit = true; + } + + terminate_connection(c, c->edge); + return; + } +} + +#ifndef HAVE_MINGW +static void sigterm_handler(void *data) { + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(((signal_t *)data)->signum)); + event_exit(); +} + +static void sighup_handler(void *data) { + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(((signal_t *)data)->signum)); + reopenlogger(); + + if(reload_configuration()) { + exit(1); + } +} + +static void sigalrm_handler(void *data) { + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(((signal_t *)data)->signum)); + retry(); +} +#endif + +int reload_configuration(void) { + char fname[PATH_MAX]; + + /* Reread our own configuration file */ + + exit_configuration(&config_tree); + init_configuration(&config_tree); + + if(!read_server_config()) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to reread configuration file."); + return EINVAL; + } + + read_config_options(config_tree, NULL); + + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, myself->name); + read_config_file(config_tree, fname, true); + + /* Parse some options that are allowed to be changed while tinc is running */ + + setup_myself_reloadable(); + + /* If StrictSubnet is set, expire deleted Subnets and read new ones in */ + + if(strictsubnets) { + for splay_each(subnet_t, subnet, subnet_tree) + if(subnet->owner) { + subnet->expires = 1; + } + } + + for splay_each(node_t, n, node_tree) { + n->status.has_address = false; + } + + load_all_nodes(); + + if(strictsubnets) { + for splay_each(subnet_t, subnet, subnet_tree) { + if(!subnet->owner) { + continue; + } + + if(subnet->expires == 1) { + send_del_subnet(everyone, subnet); + + if(subnet->owner->status.reachable) { + subnet_update(subnet->owner, subnet, false); + } + + subnet_del(subnet->owner, subnet); + } else if(subnet->expires == -1) { + subnet->expires = 0; + } else { + send_add_subnet(everyone, subnet); + + if(subnet->owner->status.reachable) { + subnet_update(subnet->owner, subnet, true); + } + } + } + } else { /* Only read our own subnets back in */ + for splay_each(subnet_t, subnet, myself->subnet_tree) + if(!subnet->expires) { + subnet->expires = 1; + } + + config_t *cfg = lookup_config(config_tree, "Subnet"); + + while(cfg) { + subnet_t *subnet, *s2; + + if(get_config_subnet(cfg, &subnet)) { + if((s2 = lookup_subnet(myself, subnet))) { + if(s2->expires == 1) { + s2->expires = 0; + } + + free_subnet(subnet); } else { - ifdebug(CONNECTIONS) logger(LOG_DEBUG, - "Error while connecting to %s (%s): %s", - c->name, c->hostname, sockstrerror(result)); - closesocket(c->socket); - do_outgoing_connection(c); - continue; + subnet_add(myself, subnet); + send_add_subnet(everyone, subnet); + subnet_update(myself, subnet, true); } } - if(!flush_meta(c)) { - terminate_connection(c, c->status.active); - continue; - } + cfg = lookup_config_next(config_tree, cfg); } - if(FD_ISSET(c->socket, readset)) { - if(!receive_meta(c)) { - c->status.tarpit = true; - terminate_connection(c, c->status.active); - continue; + for splay_each(subnet_t, subnet, myself->subnet_tree) { + if(subnet->expires == 1) { + send_del_subnet(everyone, subnet); + subnet_update(myself, subnet, false); + subnet_del(myself, subnet); } } } - for(i = 0; i < listen_sockets; i++) { - if(FD_ISSET(listen_socket[i].udp, readset)) { - handle_incoming_vpn_data(i); + /* Try to make outgoing connections */ + + try_outgoing_connections(); + + /* Close connections to hosts that have a changed or deleted host config file */ + + for list_each(connection_t, c, connection_list) { + if(c->status.control) { + continue; } - if(FD_ISSET(listen_socket[i].tcp, readset)) { - handle_new_meta_connection(listen_socket[i].tcp); + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, c->name); + struct stat s; + + if(stat(fname, &s) || s.st_mtime > last_config_check) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Host config file of %s has been changed", c->name); + terminate_connection(c, c->edge); } } + + last_config_check = now.tv_sec; + + return 0; +} + +void retry(void) { + /* Reset the reconnection timers for all outgoing connections */ + for list_each(outgoing_t, outgoing, outgoing_list) { + outgoing->timeout = 0; + + if(outgoing->ev.cb) + timeout_set(&outgoing->ev, &(struct timeval) { + 0, 0 + }); + } + + /* Check for outgoing connections that are in progress, and reset their ping timers */ + for list_each(connection_t, c, connection_list) { + if(c->outgoing && !c->node) { + c->last_ping_time = 0; + } + } + + /* Kick the ping timeout handler */ + timeout_set(&pingtimer, &(struct timeval) { + 0, 0 + }); } /* this is where it all happens... */ int main_loop(void) { - fd_set readset, writeset; -#ifdef HAVE_PSELECT - struct timespec tv; - sigset_t omask, block_mask; - time_t next_event; -#else - struct timeval tv; + last_periodic_run_time = now; + timeout_add(&pingtimer, timeout_handler, &pingtimer, &(struct timeval) { + pingtimeout, rand() % 100000 + }); + timeout_add(&periodictimer, periodic_handler, &periodictimer, &(struct timeval) { + 0, 0 + }); + +#ifndef HAVE_MINGW + signal_t sighup = {0}; + signal_t sigterm = {0}; + signal_t sigquit = {0}; + signal_t sigint = {0}; + signal_t sigalrm = {0}; + + signal_add(&sighup, sighup_handler, &sighup, SIGHUP); + signal_add(&sigterm, sigterm_handler, &sigterm, SIGTERM); + signal_add(&sigquit, sigterm_handler, &sigquit, SIGQUIT); + signal_add(&sigint, sigterm_handler, &sigint, SIGINT); + signal_add(&sigalrm, sigalrm_handler, &sigalrm, SIGALRM); #endif - int r, maxfd; - time_t last_ping_check, last_config_check, last_graph_dump; - event_t *event; - last_ping_check = now; - last_config_check = now; - last_graph_dump = now; - - srand(now); - -#ifdef HAVE_PSELECT - - if(lookup_config(config_tree, "GraphDumpFile")) { - graph_dump = true; + if(!event_loop()) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno)); + return 1; } - /* Block SIGHUP & SIGALRM */ - sigemptyset(&block_mask); - sigaddset(&block_mask, SIGHUP); - sigaddset(&block_mask, SIGALRM); - sigprocmask(SIG_BLOCK, &block_mask, &omask); +#ifndef HAVE_MINGW + signal_del(&sighup); + signal_del(&sigterm); + signal_del(&sigquit); + signal_del(&sigint); + signal_del(&sigalrm); #endif - running = true; - - while(running) { -#ifdef HAVE_PSELECT - next_event = last_ping_check + pingtimeout; - - if(graph_dump && next_event > last_graph_dump + 60) { - next_event = last_graph_dump + 60; - } - - if((event = peek_next_event()) && next_event > event->time) { - next_event = event->time; - } - - if(next_event <= now) { - tv.tv_sec = 0; - } else { - tv.tv_sec = next_event - now; - } - - tv.tv_nsec = 0; -#else - tv.tv_sec = 1; - tv.tv_usec = 0; -#endif - - maxfd = build_fdset(&readset, &writeset); - -#ifdef HAVE_MINGW - LeaveCriticalSection(&mutex); -#endif -#ifdef HAVE_PSELECT - r = pselect(maxfd + 1, &readset, &writeset, NULL, &tv, &omask); -#else - r = select(maxfd + 1, &readset, &writeset, NULL, &tv); -#endif - now = time(NULL); -#ifdef HAVE_MINGW - EnterCriticalSection(&mutex); -#endif - - if(r < 0) { - if(!sockwouldblock(sockerrno)) { - logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno)); - dump_connections(); - return 1; - } - } - - if(r > 0) { - check_network_activity(&readset, &writeset); - } - - if(do_purge) { - purge(); - do_purge = false; - } - - /* Let's check if everybody is still alive */ - - if(last_ping_check + pingtimeout <= now) { - check_dead_connections(); - last_ping_check = now; - - if(routing_mode == RMODE_SWITCH) { - age_subnets(); - } - - age_past_requests(); - - /* Should we regenerate our key? */ - - if(keyexpires <= now) { - avl_node_t *node; - node_t *n; - - ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys"); - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - - if(n->inkey) { - free(n->inkey); - n->inkey = NULL; - } - } - - send_key_changed(); - keyexpires = now + keylifetime; - } - - /* Detect ADD_EDGE/DEL_EDGE storms that are caused when - * two tinc daemons with the same name are on the VPN. - * If so, sleep a while. If this happens multiple times - * in a row, sleep longer. */ - - if(contradicting_del_edge > 100 && contradicting_add_edge > 100) { - logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime); - usleep(sleeptime * 1000000LL); - sleeptime *= 2; - - if(sleeptime < 0) { - sleeptime = 3600; - } - } else { - sleeptime /= 2; - - if(sleeptime < 10) { - sleeptime = 10; - } - } - - contradicting_add_edge = 0; - contradicting_del_edge = 0; - } - - if(sigalrm) { - avl_node_t *node; - logger(LOG_INFO, "Flushing event queue"); - expire_events(); - - for(node = connection_tree->head; node; node = node->next) { - connection_t *c = node->data; - - if(c->status.active) { - send_ping(c); - } - } - - sigalrm = false; - } - - while((event = get_expired_event())) { - event->handler(event->data); - free_event(event); - } - - if(sighup) { - connection_t *c; - avl_node_t *node, *next; - char *fname; - struct stat s; - - sighup = false; - - reopenlogger(); - - /* Reread our own configuration file */ - - exit_configuration(&config_tree); - init_configuration(&config_tree); - - if(!read_server_config()) { - logger(LOG_ERR, "Unable to reread configuration file, exitting."); - return 1; - } - - /* Cancel non-active outgoing connections */ - - for(node = connection_tree->head; node; node = next) { - next = node->next; - c = node->data; - - c->outgoing = NULL; - - if(c->status.connecting) { - terminate_connection(c, false); - connection_del(c); - } - } - - /* Wipe list of outgoing connections */ - - for(list_node_t *node = outgoing_list->head; node; node = node->next) { - outgoing_t *outgoing = node->data; - - if(outgoing->event) { - event_del(outgoing->event); - } - } - - list_delete_list(outgoing_list); - - /* Close connections to hosts that have a changed or deleted host config file */ - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); - - if(stat(fname, &s) || s.st_mtime > last_config_check) { - terminate_connection(c, c->status.active); - } - - free(fname); - } - - last_config_check = now; - - /* If StrictSubnet is set, expire deleted Subnets and read new ones in */ - - if(strictsubnets) { - subnet_t *subnet; - - for(node = subnet_tree->head; node; node = node->next) { - subnet = node->data; - subnet->expires = 1; - } - - load_all_subnets(); - - for(node = subnet_tree->head; node; node = next) { - next = node->next; - subnet = node->data; - - if(subnet->expires == 1) { - send_del_subnet(everyone, subnet); - - if(subnet->owner->status.reachable) { - subnet_update(subnet->owner, subnet, false); - } - - subnet_del(subnet->owner, subnet); - } else if(subnet->expires == -1) { - subnet->expires = 0; - } else { - send_add_subnet(everyone, subnet); - - if(subnet->owner->status.reachable) { - subnet_update(subnet->owner, subnet, true); - } - } - } - } - - /* Try to make outgoing connections */ - - try_outgoing_connections(); - } - - /* Dump graph if wanted every 60 seconds*/ - - if(last_graph_dump + 60 <= now) { - dump_graph(); - last_graph_dump = now; - } - } - -#ifdef HAVE_PSELECT - /* Restore SIGHUP & SIGALARM mask */ - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif + timeout_del(&periodictimer); + timeout_del(&pingtimer); return 0; } diff --git a/src/net.h b/src/net.h index a9becb6..cf0ddc7 100644 --- a/src/net.h +++ b/src/net.h @@ -4,7 +4,7 @@ /* net.h -- header for net.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2015 Guus Sliepen + 2000-2016 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 @@ -21,9 +21,10 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include - #include "ipv6.h" +#include "cipher.h" +#include "digest.h" +#include "event.h" #ifdef ENABLE_JUMBOGRAMS #define MTU 9018 /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ @@ -31,10 +32,13 @@ #define MTU 1518 /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ #endif -#define MAXSIZE (MTU + 4 + EVP_MAX_BLOCK_LENGTH + EVP_MAX_MD_SIZE + MTU/64 + 20) /* MTU + seqno + padding + HMAC + compressor overhead */ -#define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128) /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */ +/* MAXSIZE is the maximum size of an encapsulated packet: MTU + seqno + srcid + dstid + padding + HMAC + compressor overhead */ +#define MAXSIZE (MTU + 4 + sizeof(node_id_t) + sizeof(node_id_t) + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20) -#define MAXSOCKETS 128 /* Overkill... */ +/* MAXBUFSIZE is the maximum size of a request: enough for a MAXSIZEd packet or a 8192 bits RSA key */ +#define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128) + +#define MAXSOCKETS 8 /* Probably overkill... */ typedef struct mac_t { uint8_t x[6]; @@ -48,7 +52,12 @@ typedef struct ipv6_t { uint16_t x[8]; } ipv6_t; +typedef struct node_id_t { + uint8_t x[6]; +} node_id_t; + typedef uint16_t length_t; +typedef uint32_t seqno_t; #define AF_UNKNOWN 255 @@ -65,9 +74,6 @@ typedef union sockaddr_t { struct sockaddr_in in; struct sockaddr_in6 in6; struct sockaddr_unknown unknown; -#ifdef HAVE_STRUCT_SOCKADDR_STORAGE - struct sockaddr_storage storage; -#endif } sockaddr_t; #ifdef SA_LEN @@ -76,17 +82,36 @@ typedef union sockaddr_t { #define SALEN(s) (s.sa_family==AF_INET?sizeof(struct sockaddr_in):sizeof(struct sockaddr_in6)) #endif +#define SEQNO(x) ((x)->data + (x)->offset - 4) +#define SRCID(x) ((node_id_t *)((x)->data + (x)->offset - 6)) +#define DSTID(x) ((node_id_t *)((x)->data + (x)->offset - 12)) +#define DATA(x) ((x)->data + (x)->offset) +#define DEFAULT_PACKET_OFFSET 12 + typedef struct vpn_packet_t { - length_t len; /* the actual number of bytes in the `data' field */ + length_t len; /* The actual number of valid bytes in the `data' field (including seqno or dstid/srcid) */ + length_t offset; /* Offset in the buffer where the packet data starts (righter after seqno or dstid/srcid) */ int priority; /* priority or TOS */ - uint32_t seqno; /* 32 bits sequence number (network byte order of course) */ uint8_t data[MAXSIZE]; } vpn_packet_t; +/* Packet types when using SPTPS */ + +#define PKT_COMPRESSED 1 +#define PKT_MAC 2 +#define PKT_PROBE 4 + +typedef enum packet_type_t { + PACKET_NORMAL, + PACKET_COMPRESSED, + PACKET_PROBE +} packet_type_t; + typedef struct listen_socket_t { - int tcp; - int udp; + io_t tcp; + io_t udp; sockaddr_t sa; + bool bindto; int priority; } listen_socket_t; @@ -94,12 +119,9 @@ typedef struct listen_socket_t { #include "list.h" typedef struct outgoing_t { - char *name; + struct node_t *node; int timeout; - struct config_t *cfg; - struct addrinfo *ai; - struct addrinfo *aip; - struct event *event; + timeout_t ev; } outgoing_t; extern list_t *outgoing_list; @@ -110,52 +132,91 @@ extern int addressfamily; extern unsigned replaywin; extern bool localdiscovery; +extern bool udp_discovery; +extern int udp_discovery_keepalive_interval; +extern int udp_discovery_interval; +extern int udp_discovery_timeout; + +extern int mtu_info_interval; +extern int udp_info_interval; + extern listen_socket_t listen_socket[MAXSOCKETS]; extern int listen_sockets; -extern int keyexpires; +extern io_t unix_socket; extern int keylifetime; extern int udp_rcvbuf; extern int udp_sndbuf; +extern int max_connection_burst; +extern int fwmark; extern bool do_prune; -extern bool do_purge; extern char *myport; -extern time_t now; +extern bool device_standby; +extern bool autoconnect; +extern bool disablebuggypeers; extern int contradicting_add_edge; extern int contradicting_del_edge; +extern time_t last_config_check; -extern volatile bool running; +extern char *proxyhost; +extern char *proxyport; +extern char *proxyuser; +extern char *proxypass; +typedef enum proxytype_t { + PROXY_NONE = 0, + PROXY_SOCKS4, + PROXY_SOCKS4A, + PROXY_SOCKS5, + PROXY_HTTP, + PROXY_EXEC, +} proxytype_t; +extern proxytype_t proxytype; + +extern char *scriptinterpreter; +extern char *scriptextension; /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ #include "connection.h" #include "node.h" extern void retry_outgoing(outgoing_t *outgoing); -extern void handle_incoming_vpn_data(int sock); +extern void handle_incoming_vpn_data(void *data, int flags); extern void finish_connecting(struct connection_t *c); -extern void do_outgoing_connection(struct connection_t *c); -extern bool handle_new_meta_connection(int sock); +extern bool do_outgoing_connection(struct outgoing_t *outgoing); +extern void handle_new_meta_connection(void *data, int flags); +extern void handle_new_unix_connection(void *data, int flags); extern int setup_listen_socket(const sockaddr_t *sa); extern int setup_vpn_in_socket(const sockaddr_t *sa); -extern void send_packet(const struct node_t *n, vpn_packet_t *packet); -extern void receive_tcppacket(struct connection_t *c, const char *buffer, length_t len); -extern void broadcast_packet(const struct node_t *, vpn_packet_t *packet); +extern bool send_sptps_data(node_t *to, node_t *from, int type, const void *data, size_t len); +extern bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len); +extern void send_packet(struct node_t *n, vpn_packet_t *packet); +extern void receive_tcppacket(struct connection_t *c, const char *buffer, size_t length); +extern bool receive_tcppacket_sptps(struct connection_t *c, const char *buffer, size_t length); +extern void broadcast_packet(const struct node_t *n, vpn_packet_t *packet); extern char *get_name(void); +extern void device_enable(void); +extern void device_disable(void); +extern bool setup_myself_reloadable(void); extern bool setup_network(void); -extern void setup_outgoing_connection(struct outgoing_t *outgoing); +extern void setup_outgoing_connection(struct outgoing_t *outgoing, bool verbose); extern void try_outgoing_connections(void); extern void close_network_connections(void); extern int main_loop(void); extern void terminate_connection(struct connection_t *c, bool report); -extern void flush_queue(struct node_t *n); +extern bool node_read_ecdsa_public_key(struct node_t *n); +extern bool read_ecdsa_public_key(struct connection_t *c); extern bool read_rsa_public_key(struct connection_t *c); -extern void send_mtu_probe(struct node_t *n); -extern void load_all_subnets(void); +extern void handle_device_data(void *data, int flags); +extern void handle_meta_connection_data(struct connection_t *c); +extern void regenerate_key(void); +extern void purge(void); +extern void retry(void); +extern int reload_configuration(void); +extern void load_all_nodes(void); +extern void try_tx(struct node_t *n, bool mtu); extern void tarpit(int fd); #ifndef HAVE_MINGW #define closesocket(s) close(s) -#else -extern CRITICAL_SECTION mutex; #endif #endif diff --git a/src/net_packet.c b/src/net_packet.c index 938b3b6..c2132b8 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -1,7 +1,7 @@ /* net_packet.c -- Handles in- and outgoing VPN packets Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2021 Guus Sliepen 2010 Timothy Redaelli 2010 Brandon Black @@ -22,13 +22,8 @@ #include "system.h" -#include -#include -#include -#include -#include - #ifdef HAVE_ZLIB +#define ZLIB_CONST #include #endif @@ -36,84 +31,56 @@ #include LZO1X_H #endif -#include "avl_tree.h" +#include "address_cache.h" +#include "cipher.h" #include "conf.h" #include "connection.h" +#include "crypto.h" +#include "digest.h" #include "device.h" #include "ethernet.h" -#include "event.h" +#include "ipv4.h" +#include "ipv6.h" #include "graph.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "protocol.h" -#include "process.h" #include "route.h" #include "utils.h" #include "xalloc.h" +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +/* The minimum size of a probe is 14 bytes, but since we normally use CBC mode + encryption, we can add a few extra random bytes without increasing the + resulting packet size. */ +#define MIN_PROBE_SIZE 18 + int keylifetime = 0; -int keyexpires = 0; #ifdef HAVE_LZO static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS]; #endif static void send_udppacket(node_t *, vpn_packet_t *); -unsigned replaywin = 16; -bool localdiscovery = false; +unsigned replaywin = 32; +bool localdiscovery = true; +bool udp_discovery = true; +int udp_discovery_keepalive_interval = 10; +int udp_discovery_interval = 2; +int udp_discovery_timeout = 30; #define MAX_SEQNO 1073741824 -/* mtuprobes == 1..30: initial discovery, send bursts with 1 second interval - mtuprobes == 31: sleep pinginterval seconds - mtuprobes == 32: send 1 burst, sleep pingtimeout second - mtuprobes == 33: no response from other side, restart PMTU discovery process - - Probes are sent in batches of at least three, with random sizes between the - lower and upper boundaries for the MTU thus far discovered. - - After the initial discovery, a fourth packet is added to each batch with a - size larger than the currently known PMTU, to test if the PMTU has increased. - - In case local discovery is enabled, another packet is added to each batch, - which will be broadcast to the local network. - -*/ - -void send_mtu_probe(node_t *n) { - vpn_packet_t packet; - int len, i; - int timeout = 1; - - n->mtuprobes++; - n->mtuevent = NULL; - - if(!n->status.reachable || !n->status.validkey) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname); - n->mtuprobes = 0; +static void try_fix_mtu(node_t *n) { + if(n->mtuprobes < 0) { return; } - if(n->mtuprobes > 32) { - if(!n->minmtu) { - n->mtuprobes = 31; - timeout = pinginterval; - goto end; - } - - ifdebug(TRAFFIC) logger(LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname); - n->mtuprobes = 1; - n->minmtu = 0; - n->maxmtu = MTU; - } - - if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) { - ifdebug(TRAFFIC) logger(LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname); - n->mtuprobes = 31; - } - - if(n->mtuprobes == 30 || (n->mtuprobes < 30 && n->minmtu >= n->maxmtu)) { + if(n->mtuprobes == 20 || n->minmtu >= n->maxmtu) { if(n->minmtu > n->maxmtu) { n->minmtu = n->maxmtu; } else { @@ -121,86 +88,121 @@ void send_mtu_probe(node_t *n) { } n->mtu = n->minmtu; - ifdebug(TRAFFIC) logger(LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); - n->mtuprobes = 31; + logger(DEBUG_TRAFFIC, LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); + n->mtuprobes = -1; } - - if(n->mtuprobes == 31) { - timeout = pinginterval; - goto end; - } else if(n->mtuprobes == 32) { - timeout = pingtimeout; - } - - for(i = 0; i < 4 + localdiscovery; i++) { - if(i == 0) { - if(n->mtuprobes < 30 || n->maxmtu + 8 >= MTU) { - continue; - } - - len = n->maxmtu + 8; - } else if(n->maxmtu <= n->minmtu) { - len = n->maxmtu; - } else { - len = n->minmtu + 1 + rand() % (n->maxmtu - n->minmtu); - } - - if(len < 64) { - len = 64; - } - - memset(packet.data, 0, 14); - RAND_bytes(packet.data + 14, len - 14); - packet.len = len; - - if(i >= 4 && n->mtuprobes <= 10) { - packet.priority = -1; - } else { - packet.priority = 0; - } - - ifdebug(TRAFFIC) logger(LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname); - - send_udppacket(n, &packet); - } - -end: - n->mtuevent = new_event(); - n->mtuevent->handler = (event_handler_t)send_mtu_probe; - n->mtuevent->data = n; - n->mtuevent->time = now + timeout; - event_add(n->mtuevent); } -void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname); +static void udp_probe_timeout_handler(void *data) { + node_t *n = data; + + if(!n->status.udp_confirmed) { + return; + } + + logger(DEBUG_TRAFFIC, LOG_INFO, "Too much time has elapsed since last UDP ping response from %s (%s), stopping UDP communication", n->name, n->hostname); + n->status.udp_confirmed = false; + n->udp_ping_rtt = -1; + n->maxrecentlen = 0; + n->mtuprobes = 0; + n->minmtu = 0; + n->maxmtu = MTU; +} + +static void send_udp_probe_reply(node_t *n, vpn_packet_t *packet, length_t len) { + if(!n->status.sptps && !n->status.validkey) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP probe reply to %s (%s) but we don't have his key yet", n->name, n->hostname); + return; + } + + /* Type 2 probe replies were introduced in protocol 17.3 */ + if((n->options >> 24) >= 3) { + DATA(packet)[0] = 2; + uint16_t len16 = htons(len); + memcpy(DATA(packet) + 1, &len16, 2); + packet->len = MIN_PROBE_SIZE; + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending type 2 probe reply length %u to %s (%s)", len, n->name, n->hostname); - if(!packet->data[0]) { - packet->data[0] = 1; - send_udppacket(n, packet); } else { - if(n->mtuprobes > 30) { - if(len == n->maxmtu + 8) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Increase in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname); - n->maxmtu = MTU; - n->mtuprobes = 10; - return; - } + /* Legacy protocol: n won't understand type 2 probe replies. */ + DATA(packet)[0] = 1; + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending type 1 probe reply length %u to %s (%s)", len, n->name, n->hostname); + } - if(n->minmtu) { - n->mtuprobes = 30; - } else { - n->mtuprobes = 1; - } + /* Temporarily set udp_confirmed, so that the reply is sent + back exactly the way it came in. */ + + bool udp_confirmed = n->status.udp_confirmed; + n->status.udp_confirmed = true; + send_udppacket(n, packet); + n->status.udp_confirmed = udp_confirmed; +} + +static void udp_probe_h(node_t *n, vpn_packet_t *packet, length_t len) { + if(!DATA(packet)[0]) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Got UDP probe request %d from %s (%s)", packet->len, n->name, n->hostname); + send_udp_probe_reply(n, packet, len); + return; + } + + if(DATA(packet)[0] == 2) { + // It's a type 2 probe reply, use the length field inside the packet + uint16_t len16; + memcpy(&len16, DATA(packet) + 1, 2); + len = ntohs(len16); + } + + if(n->status.ping_sent) { // a probe in flight + gettimeofday(&now, NULL); + struct timeval rtt; + timersub(&now, &n->udp_ping_sent, &rtt); + n->udp_ping_rtt = rtt.tv_sec * 1000000 + rtt.tv_usec; + n->status.ping_sent = false; + logger(DEBUG_TRAFFIC, LOG_INFO, "Got type %d UDP probe reply %d from %s (%s) rtt=%d.%03d", DATA(packet)[0], len, n->name, n->hostname, n->udp_ping_rtt / 1000, n->udp_ping_rtt % 1000); + } else { + logger(DEBUG_TRAFFIC, LOG_INFO, "Got type %d UDP probe reply %d from %s (%s)", DATA(packet)[0], len, n->name, n->hostname); + } + + /* It's a valid reply: now we know bidirectional communication + is possible using the address and socket that the reply + packet used. */ + if(!n->status.udp_confirmed) { + n->status.udp_confirmed = true; + + if(!n->address_cache) { + n->address_cache = open_address_cache(n); } - if(len > n->maxmtu) { - len = n->maxmtu; - } + reset_address_cache(n->address_cache, &n->address); + } - if(n->minmtu < len) { - n->minmtu = len; - } + // Reset the UDP ping timer. + + if(udp_discovery) { + timeout_del(&n->udp_ping_timeout); + timeout_add(&n->udp_ping_timeout, &udp_probe_timeout_handler, n, &(struct timeval) { + udp_discovery_timeout, 0 + }); + } + + if(len > n->maxmtu) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Increase in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname); + n->minmtu = len; + n->maxmtu = MTU; + /* Set mtuprobes to 1 so that try_mtu() doesn't reset maxmtu */ + n->mtuprobes = 1; + return; + } else if(n->mtuprobes < 0 && len == n->maxmtu) { + /* We got a maxmtu sized packet, confirming the PMTU is still valid. */ + n->mtuprobes = -1; + n->mtu_ping_sent = now; + } + + /* If applicable, raise the minimum supported MTU */ + + if(n->minmtu < len) { + n->minmtu = len; + try_fix_mtu(n); } } @@ -256,9 +258,22 @@ static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t #ifdef HAVE_ZLIB else { unsigned long destlen = MAXSIZE; + static z_stream stream; - if(uncompress(dest, &destlen, source, len) == Z_OK) { - return destlen; + if(stream.next_in) { + inflateReset(&stream); + } else { + inflateInit(&stream); + } + + stream.next_in = source; + stream.avail_in = len; + stream.next_out = dest; + stream.avail_out = destlen; + stream.total_out = 0; + + if(inflate(&stream, Z_FINISH) == Z_STREAM_END) { + return stream.total_out; } else { return 0; } @@ -266,124 +281,168 @@ static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t #endif - return -1; + return 0; } /* VPN packet I/O */ static void receive_packet(node_t *n, vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Received packet of %d bytes from %s (%s)", - packet->len, n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Received packet of %d bytes from %s (%s)", + packet->len, n->name, n->hostname); + + n->in_packets++; + n->in_bytes += packet->len; route(n, packet); } -static bool try_mac(const node_t *n, const vpn_packet_t *inpkt) { - unsigned char hmac[EVP_MAX_MD_SIZE]; +static bool try_mac(node_t *n, const vpn_packet_t *inpkt) { + if(n->status.sptps) { + return sptps_verify_datagram(&n->sptps, DATA(inpkt), inpkt->len); + } - if(!n->indigest || !n->inmaclength || !n->inkey || inpkt->len < sizeof(inpkt->seqno) + n->inmaclength) { +#ifdef DISABLE_LEGACY + return false; +#else + + if(!n->status.validkey_in || !digest_active(n->indigest) || (size_t)inpkt->len < sizeof(seqno_t) + digest_length(n->indigest)) { return false; } - HMAC(n->indigest, n->inkey, n->inkeylength, (unsigned char *) &inpkt->seqno, inpkt->len - n->inmaclength, (unsigned char *)hmac, NULL); - - return !memcmp_constant_time(hmac, (char *) &inpkt->seqno + inpkt->len - n->inmaclength, n->inmaclength); + return digest_verify(n->indigest, inpkt->data, inpkt->len - digest_length(n->indigest), inpkt->data + inpkt->len - digest_length(n->indigest)); +#endif } -static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { +static bool receive_udppacket(node_t *n, vpn_packet_t *inpkt) { + if(n->status.sptps) { + if(!n->sptps.state) { + if(!n->status.waitingforkey) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but we haven't exchanged keys yet", n->name, n->hostname); + send_req_key(n); + } else { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); + } + + return false; + } + + n->status.udppacket = true; + bool result = sptps_receive_data(&n->sptps, DATA(inpkt), inpkt->len); + n->status.udppacket = false; + + if(!result) { + /* Uh-oh. It might be that the tunnel is stuck in some corrupted state, + so let's restart SPTPS in case that helps. But don't do that too often + to prevent storms, and because that would make life a little too easy + for external attackers trying to DoS us. */ + if(n->last_req_key < now.tv_sec - 10) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Failed to decode raw TCP packet from %s (%s), restarting SPTPS", n->name, n->hostname); + send_req_key(n); + } + + return false; + } + + return true; + } + +#ifdef DISABLE_LEGACY + return false; +#else vpn_packet_t pkt1, pkt2; vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 }; int nextpkt = 0; - vpn_packet_t *outpkt; - int outlen, outpad; - unsigned char hmac[EVP_MAX_MD_SIZE]; + size_t outlen; + pkt1.offset = DEFAULT_PACKET_OFFSET; + pkt2.offset = DEFAULT_PACKET_OFFSET; - if(!n->inkey) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", - n->name, n->hostname); - return; + if(!n->status.validkey_in) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); + return false; } /* Check packet length */ - if(inpkt->len < sizeof(inpkt->seqno) + n->inmaclength) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got too short packet from %s (%s)", - n->name, n->hostname); - return; + if((size_t)inpkt->len < sizeof(seqno_t) + digest_length(n->indigest)) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got too short packet from %s (%s)", + n->name, n->hostname); + return false; } + /* It's a legacy UDP packet, the data starts after the seqno */ + + inpkt->offset += sizeof(seqno_t); + /* Check the message authentication code */ - if(n->indigest && n->inmaclength) { - inpkt->len -= n->inmaclength; - HMAC(n->indigest, n->inkey, n->inkeylength, - (unsigned char *) &inpkt->seqno, inpkt->len, (unsigned char *)hmac, NULL); + if(digest_active(n->indigest)) { + inpkt->len -= digest_length(n->indigest); - if(memcmp_constant_time(hmac, (char *) &inpkt->seqno + inpkt->len, n->inmaclength)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)", - n->name, n->hostname); - return; + if(!digest_verify(n->indigest, SEQNO(inpkt), inpkt->len, SEQNO(inpkt) + inpkt->len)) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got unauthenticated packet from %s (%s)", n->name, n->hostname); + return false; } } /* Decrypt the packet */ - if(n->incipher) { - outpkt = pkt[nextpkt++]; + if(cipher_active(n->incipher)) { + vpn_packet_t *outpkt = pkt[nextpkt++]; + outlen = MAXSIZE; - if(!EVP_DecryptInit_ex(n->inctx, NULL, NULL, NULL, NULL) - || !EVP_DecryptUpdate(n->inctx, (unsigned char *) &outpkt->seqno, &outlen, - (unsigned char *) &inpkt->seqno, inpkt->len) - || !EVP_DecryptFinal_ex(n->inctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s): %s", - n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL)); - return; + if(!cipher_decrypt(n->incipher, SEQNO(inpkt), inpkt->len, SEQNO(outpkt), &outlen, true)) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Error decrypting packet from %s (%s)", n->name, n->hostname); + return false; } - outpkt->len = outlen + outpad; + outpkt->len = outlen; inpkt = outpkt; } /* Check the sequence number */ - inpkt->len -= sizeof(inpkt->seqno); - inpkt->seqno = ntohl(inpkt->seqno); + seqno_t seqno; + memcpy(&seqno, SEQNO(inpkt), sizeof(seqno)); + seqno = ntohl(seqno); + inpkt->len -= sizeof(seqno); if(replaywin) { - if(inpkt->seqno != n->received_seqno + 1) { - if(inpkt->seqno >= n->received_seqno + replaywin * 8) { + if(seqno != n->received_seqno + 1) { + if(seqno >= n->received_seqno + replaywin * 8) { if(n->farfuture++ < replaywin >> 2) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)", - n->name, n->hostname, inpkt->seqno - n->received_seqno - 1, n->farfuture); - return; + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)", + n->name, n->hostname, seqno - n->received_seqno - 1, n->farfuture); + return false; } - ifdebug(TRAFFIC) logger(LOG_WARNING, "Lost %d packets from %s (%s)", - inpkt->seqno - n->received_seqno - 1, n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Lost %d packets from %s (%s)", + seqno - n->received_seqno - 1, n->name, n->hostname); memset(n->late, 0, replaywin); - } else if(inpkt->seqno <= n->received_seqno) { - if((n->received_seqno >= replaywin * 8 && inpkt->seqno <= n->received_seqno - replaywin * 8) || !(n->late[(inpkt->seqno / 8) % replaywin] & (1 << inpkt->seqno % 8))) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d", - n->name, n->hostname, inpkt->seqno, n->received_seqno); - return; + } else if(seqno <= n->received_seqno) { + if((n->received_seqno >= replaywin * 8 && seqno <= n->received_seqno - replaywin * 8) || !(n->late[(seqno / 8) % replaywin] & (1 << seqno % 8))) { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d", + n->name, n->hostname, seqno, n->received_seqno); + return false; } } else { - for(uint32_t i = n->received_seqno + 1; i < inpkt->seqno; i++) { + for(seqno_t i = n->received_seqno + 1; i < seqno; i++) { n->late[(i / 8) % replaywin] |= 1 << i % 8; } } } n->farfuture = 0; - n->late[(inpkt->seqno / 8) % replaywin] &= ~(1 << inpkt->seqno % 8); + n->late[(seqno / 8) % replaywin] &= ~(1 << seqno % 8); } - if(inpkt->seqno > n->received_seqno) { - n->received_seqno = inpkt->seqno; + if(seqno > n->received_seqno) { + n->received_seqno = seqno; } + n->received++; + if(n->received_seqno > MAX_SEQNO) { - keyexpires = 0; + regenerate_key(); } /* Decompress the packet */ @@ -391,32 +450,44 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { length_t origlen = inpkt->len; if(n->incompression) { - outpkt = pkt[nextpkt++]; + vpn_packet_t *outpkt = pkt[nextpkt++]; - if(!(outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression))) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while uncompressing packet from %s (%s)", - n->name, n->hostname); - return; + if(!(outpkt->len = uncompress_packet(DATA(outpkt), DATA(inpkt), inpkt->len, n->incompression))) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while uncompressing packet from %s (%s)", + n->name, n->hostname); + return false; } inpkt = outpkt; - origlen -= MTU / 64 + 20; + if(origlen > MTU / 64 + 20) { + origlen -= MTU / 64 + 20; + } else { + origlen = 0; + } + } + + if(inpkt->len > n->maxrecentlen) { + n->maxrecentlen = inpkt->len; } inpkt->priority = 0; - if(!inpkt->data[12] && !inpkt->data[13]) { - mtu_probe_h(n, inpkt, origlen); + if(!DATA(inpkt)[12] && !DATA(inpkt)[13]) { + udp_probe_h(n, inpkt, origlen); } else { receive_packet(n, inpkt); } + + return true; +#endif } -void receive_tcppacket(connection_t *c, const char *buffer, length_t len) { +void receive_tcppacket(connection_t *c, const char *buffer, size_t len) { vpn_packet_t outpkt; + outpkt.offset = DEFAULT_PACKET_OFFSET; - if(len > sizeof(outpkt.data)) { + if(len > sizeof(outpkt.data) - outpkt.offset) { return; } @@ -428,47 +499,247 @@ void receive_tcppacket(connection_t *c, const char *buffer, length_t len) { outpkt.priority = -1; } - memcpy(outpkt.data, buffer, len); + memcpy(DATA(&outpkt), buffer, len); receive_packet(c->node, &outpkt); } +bool receive_tcppacket_sptps(connection_t *c, const char *data, size_t len) { + if(len < sizeof(node_id_t) + sizeof(node_id_t)) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Got too short TCP SPTPS packet from %s (%s)", c->name, c->hostname); + return false; + } + + node_t *to = lookup_node_id((node_id_t *)data); + data += sizeof(node_id_t); + len -= sizeof(node_id_t); + + if(!to) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Got TCP SPTPS packet from %s (%s) with unknown destination ID", c->name, c->hostname); + return true; + } + + node_t *from = lookup_node_id((node_id_t *)data); + data += sizeof(node_id_t); + len -= sizeof(node_id_t); + + if(!from) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Got TCP SPTPS packet from %s (%s) with unknown source ID", c->name, c->hostname); + return true; + } + + if(!to->status.reachable) { + /* This can happen in the form of a race condition + if the node just became unreachable. */ + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot relay TCP packet from %s (%s) because the destination, %s (%s), is unreachable", from->name, from->hostname, to->name, to->hostname); + return true; + } + + /* Help the sender reach us over UDP. + Note that we only do this if we're the destination or the static relay; + otherwise every hop would initiate its own UDP info message, resulting in elevated chatter. */ + if(to->via == myself) { + send_udp_info(myself, from); + } + + /* If we're not the final recipient, relay the packet. */ + + if(to != myself) { + if(to->status.validkey) { + send_sptps_data(to, from, 0, data, len); + } + + try_tx(to, true); + return true; + } + + /* The packet is for us */ + + if(!sptps_receive_data(&from->sptps, data, len)) { + /* Uh-oh. It might be that the tunnel is stuck in some corrupted state, + so let's restart SPTPS in case that helps. But don't do that too often + to prevent storms. */ + if(from->last_req_key < now.tv_sec - 10) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Failed to decode raw TCP packet from %s (%s), restarting SPTPS", from->name, from->hostname); + send_req_key(from); + } + + return true; + } + + send_mtu_info(myself, from, MTU); + return true; +} + +static void send_sptps_packet(node_t *n, vpn_packet_t *origpkt) { + if(!n->status.validkey && !n->connection) { + return; + } + + uint8_t type = 0; + int offset = 0; + + if((!(DATA(origpkt)[12] | DATA(origpkt)[13])) && (n->sptps.outstate)) { + sptps_send_record(&n->sptps, PKT_PROBE, (char *)DATA(origpkt), origpkt->len); + return; + } + + if(routing_mode == RMODE_ROUTER) { + offset = 14; + } else { + type = PKT_MAC; + } + + if(origpkt->len < offset) { + return; + } + + vpn_packet_t outpkt; + + if(n->outcompression) { + outpkt.offset = 0; + length_t len = compress_packet(DATA(&outpkt) + offset, DATA(origpkt) + offset, origpkt->len - offset, n->outcompression); + + if(!len) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", n->name, n->hostname); + } else if(len < origpkt->len - offset) { + outpkt.len = len + offset; + origpkt = &outpkt; + type |= PKT_COMPRESSED; + } + } + + /* If we have a direct metaconnection to n, and we can't use UDP, then + don't bother with SPTPS and just use a "plaintext" PACKET message. + We don't really care about end-to-end security since we're not + sending the message through any intermediate nodes. */ + if(n->connection && origpkt->len > n->minmtu) { + send_tcppacket(n->connection, origpkt); + } else { + sptps_send_record(&n->sptps, type, DATA(origpkt) + offset, origpkt->len - offset); + } + + return; +} + +static void adapt_socket(const sockaddr_t *sa, int *sock) { + /* Make sure we have a suitable socket for the chosen address */ + if(listen_socket[*sock].sa.sa.sa_family != sa->sa.sa_family) { + for(int i = 0; i < listen_sockets; i++) { + if(listen_socket[i].sa.sa.sa_family == sa->sa.sa_family) { + *sock = i; + break; + } + } + } +} + +static void choose_udp_address(const node_t *n, const sockaddr_t **sa, int *sock) { + /* Latest guess */ + *sa = &n->address; + *sock = n->sock; + + /* If the UDP address is confirmed, use it. */ + if(n->status.udp_confirmed) { + return; + } + + /* Send every third packet to n->address; that could be set + to the node's reflexive UDP address discovered during key + exchange. */ + + static int x = 0; + + if(++x >= 3) { + x = 0; + return; + } + + /* Otherwise, address are found in edges to this node. + So we pick a random edge and a random socket. */ + + int i = 0; + int j = rand() % n->edge_tree->count; + edge_t *candidate = NULL; + + for splay_each(edge_t, e, n->edge_tree) { + if(i++ == j) { + candidate = e->reverse; + break; + } + } + + if(candidate) { + *sa = &candidate->address; + *sock = rand() % listen_sockets; + } + + adapt_socket(*sa, sock); +} + +static void choose_local_address(const node_t *n, const sockaddr_t **sa, int *sock) { + *sa = NULL; + + /* Pick one of the edges from this node at random, then use its local address. */ + + int i = 0; + int j = rand() % n->edge_tree->count; + edge_t *candidate = NULL; + + for splay_each(edge_t, e, n->edge_tree) { + if(i++ == j) { + candidate = e; + break; + } + } + + if(candidate && candidate->local_address.sa.sa_family) { + *sa = &candidate->local_address; + *sock = rand() % listen_sockets; + adapt_socket(*sa, sock); + } +} + static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { + if(!n->status.reachable) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); + return; + } + + if(n->status.sptps) { + send_sptps_packet(n, origpkt); + return; + } + +#ifdef DISABLE_LEGACY + return; +#else vpn_packet_t pkt1, pkt2; vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 }; vpn_packet_t *inpkt = origpkt; int nextpkt = 0; vpn_packet_t *outpkt; - int origlen; - int outlen, outpad; - int origpriority; + int origlen = origpkt->len; + size_t outlen; + int origpriority = origpkt->priority; - if(!n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); - return; - } + pkt1.offset = DEFAULT_PACKET_OFFSET; + pkt2.offset = DEFAULT_PACKET_OFFSET; /* Make sure we have a valid key */ if(!n->status.validkey) { - ifdebug(TRAFFIC) logger(LOG_INFO, - "No valid key known yet for %s (%s), forwarding via TCP", - n->name, n->hostname); - - if(n->last_req_key + 10 <= now) { - send_req_key(n); - n->last_req_key = now; - } - + logger(DEBUG_TRAFFIC, LOG_INFO, + "No valid key known yet for %s (%s), forwarding via TCP", + n->name, n->hostname); send_tcppacket(n->nexthop->connection, origpkt); - return; } - if(n->options & OPTION_PMTU_DISCOVERY && inpkt->len > n->minmtu && (inpkt->data[12] | inpkt->data[13])) { - ifdebug(TRAFFIC) logger(LOG_INFO, - "Packet for %s (%s) larger than minimum MTU, forwarding via %s", - n->name, n->hostname, n != n->nexthop ? n->nexthop->name : "TCP"); + if(n->options & OPTION_PMTU_DISCOVERY && inpkt->len > n->minmtu && (DATA(inpkt)[12] | DATA(inpkt)[13])) { + logger(DEBUG_TRAFFIC, LOG_INFO, + "Packet for %s (%s) larger than minimum MTU, forwarding via %s", + n->name, n->hostname, n != n->nexthop ? n->nexthop->name : "TCP"); if(n != n->nexthop) { send_packet(n->nexthop, origpkt); @@ -479,17 +750,14 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { return; } - origlen = inpkt->len; - origpriority = inpkt->priority; - /* Compress the packet */ if(n->outcompression) { outpkt = pkt[nextpkt++]; - if(!(outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression))) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while compressing packet to %s (%s)", - n->name, n->hostname); + if(!(outpkt->len = compress_packet(DATA(outpkt), DATA(inpkt), inpkt->len, n->outcompression))) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", + n->name, n->hostname); return; } @@ -498,95 +766,60 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { /* Add sequence number */ - inpkt->seqno = htonl(++(n->sent_seqno)); - inpkt->len += sizeof(inpkt->seqno); + seqno_t seqno = htonl(++(n->sent_seqno)); + memcpy(SEQNO(inpkt), &seqno, sizeof(seqno)); + inpkt->len += sizeof(seqno); /* Encrypt the packet */ - if(n->outcipher) { + if(cipher_active(n->outcipher)) { outpkt = pkt[nextpkt++]; + outlen = MAXSIZE; - if(!EVP_EncryptInit_ex(n->outctx, NULL, NULL, NULL, NULL) - || !EVP_EncryptUpdate(n->outctx, (unsigned char *) &outpkt->seqno, &outlen, - (unsigned char *) &inpkt->seqno, inpkt->len) - || !EVP_EncryptFinal_ex(n->outctx, (unsigned char *) &outpkt->seqno + outlen, &outpad)) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s): %s", - n->name, n->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!cipher_encrypt(n->outcipher, SEQNO(inpkt), inpkt->len, SEQNO(outpkt), &outlen, true)) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname); goto end; } - outpkt->len = outlen + outpad; + outpkt->len = outlen; inpkt = outpkt; } /* Add the message authentication code */ - if(n->outdigest && n->outmaclength) { - HMAC(n->outdigest, n->outkey, n->outkeylength, (unsigned char *) &inpkt->seqno, - inpkt->len, (unsigned char *) &inpkt->seqno + inpkt->len, NULL); - inpkt->len += n->outmaclength; - } - - /* Determine which socket we have to use */ - - if(n->address.sa.sa_family != listen_socket[n->sock].sa.sa.sa_family) { - for(int sock = 0; sock < listen_sockets; sock++) { - if(n->address.sa.sa_family == listen_socket[sock].sa.sa.sa_family) { - n->sock = sock; - break; - } + if(digest_active(n->outdigest)) { + if(!digest_create(n->outdigest, SEQNO(inpkt), inpkt->len, SEQNO(inpkt) + inpkt->len)) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname); + goto end; } + + inpkt->len += digest_length(n->outdigest); } /* Send the packet */ - struct sockaddr *sa; - socklen_t sl; + const sockaddr_t *sa = NULL; int sock; - sockaddr_t broadcast; - /* Overloaded use of priority field: -1 means local broadcast */ - - if(origpriority == -1 && n->prevedge) { - sock = rand() % listen_sockets; - memset(&broadcast, 0, sizeof(broadcast)); - - if(listen_socket[sock].sa.sa.sa_family == AF_INET6) { - broadcast.in6.sin6_family = AF_INET6; - broadcast.in6.sin6_addr.s6_addr[0x0] = 0xff; - broadcast.in6.sin6_addr.s6_addr[0x1] = 0x02; - broadcast.in6.sin6_addr.s6_addr[0xf] = 0x01; - broadcast.in6.sin6_port = n->prevedge->address.in.sin_port; - broadcast.in6.sin6_scope_id = listen_socket[sock].sa.in6.sin6_scope_id; - } else { - broadcast.in.sin_family = AF_INET; - broadcast.in.sin_addr.s_addr = -1; - broadcast.in.sin_port = n->prevedge->address.in.sin_port; - } - - sa = &broadcast.sa; - sl = SALEN(broadcast.sa); - } else { - if(origpriority == -1) { - origpriority = 0; - } - - sa = &(n->address.sa); - sl = SALEN(n->address.sa); - sock = n->sock; + if(n->status.send_locally) { + choose_local_address(n, &sa, &sock); } - if(priorityinheritance && origpriority != listen_socket[n->sock].priority) { - listen_socket[n->sock].priority = origpriority; + if(!sa) { + choose_udp_address(n, &sa, &sock); + } - switch(listen_socket[n->sock].sa.sa.sa_family) { + if(priorityinheritance && origpriority != listen_socket[sock].priority) { + listen_socket[sock].priority = origpriority; + + switch(sa->sa.sa_family) { #if defined(IP_TOS) case AF_INET: - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Setting IPv4 outgoing packet priority to %d", origpriority); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting IPv4 outgoing packet priority to %d", origpriority); - if(setsockopt(listen_socket[n->sock].udp, IPPROTO_IP, IP_TOS, (void *)&origpriority, sizeof(origpriority))) { /* SO_PRIORITY doesn't seem to work */ - logger(LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno)); + if(setsockopt(listen_socket[sock].udp.fd, IPPROTO_IP, IP_TOS, (void *)&origpriority, sizeof(origpriority))) { /* SO_PRIORITY doesn't seem to work */ + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno)); } break; @@ -594,10 +827,10 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { #if defined(IPV6_TCLASS) case AF_INET6: - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Setting IPv6 outgoing packet priority to %d", origpriority); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting IPv6 outgoing packet priority to %d", origpriority); - if(setsockopt(listen_socket[n->sock].udp, IPPROTO_IPV6, IPV6_TCLASS, (void *)&origpriority, sizeof(origpriority))) { - logger(LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno)); + if(setsockopt(listen_socket[sock].udp.fd, IPPROTO_IPV6, IPV6_TCLASS, (void *)&origpriority, sizeof(origpriority))) { /* SO_PRIORITY doesn't seem to work */ + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno)); } break; @@ -608,7 +841,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { } } - if(sendto(listen_socket[sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, sa, sl) < 0 && !sockwouldblock(sockerrno)) { + if(sendto(listen_socket[sock].udp.fd, (void *)SEQNO(inpkt), inpkt->len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) { if(sockmsgsize(sockerrno)) { if(n->maxmtu >= origlen) { n->maxmtu = origlen - 1; @@ -617,61 +850,663 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { if(n->mtu >= origlen) { n->mtu = origlen - 1; } + + try_fix_mtu(n); } else { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno)); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno)); } } end: origpkt->len = origlen; +#endif } -/* - send a packet to the given vpn ip. +bool send_sptps_data(node_t *to, node_t *from, int type, const void *data, size_t len) { + node_t *relay = (to->via != myself && (type == PKT_PROBE || (len - SPTPS_DATAGRAM_OVERHEAD) <= to->via->minmtu)) ? to->via : to->nexthop; + bool direct = from == myself && to == relay; + bool relay_supported = (relay->options >> 24) >= 4; + bool tcponly = (myself->options | relay->options) & OPTION_TCPONLY; + + /* Send it via TCP if it is a handshake packet, TCPOnly is in use, this is a relay packet that the other node cannot understand, or this packet is larger than the MTU. */ + + if(type == SPTPS_HANDSHAKE || tcponly || (!direct && !relay_supported) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > relay->minmtu)) { + if(type != SPTPS_HANDSHAKE && (to->nexthop->connection->options >> 24) >= 7) { + char buf[len + sizeof(to->id) + sizeof(from->id)]; + char *buf_ptr = buf; + memcpy(buf_ptr, &to->id, sizeof(to->id)); + buf_ptr += sizeof(to->id); + memcpy(buf_ptr, &from->id, sizeof(from->id)); + buf_ptr += sizeof(from->id); + memcpy(buf_ptr, data, len); + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet from %s (%s) to %s (%s) via %s (%s) (TCP)", from->name, from->hostname, to->name, to->hostname, to->nexthop->name, to->nexthop->hostname); + return send_sptps_tcppacket(to->nexthop->connection, buf, sizeof(buf)); + } + + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + + /* If this is a handshake packet, use ANS_KEY instead of REQ_KEY, for two reasons: + - We don't want intermediate nodes to switch to UDP to relay these packets; + - ANS_KEY allows us to learn the reflexive UDP address. */ + if(type == SPTPS_HANDSHAKE) { + to->incompression = myself->incompression; + return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, from->name, to->name, buf, to->incompression); + } else { + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, from->name, to->name, SPTPS_PACKET, buf); + } + } + + size_t overhead = 0; + + if(relay_supported) { + overhead += sizeof(to->id) + sizeof(from->id); + } + + char buf[len + overhead]; + char *buf_ptr = buf; + + if(relay_supported) { + if(direct) { + /* Inform the recipient that this packet was sent directly. */ + node_id_t nullid = {0}; + memcpy(buf_ptr, &nullid, sizeof(nullid)); + buf_ptr += sizeof(nullid); + } else { + memcpy(buf_ptr, &to->id, sizeof(to->id)); + buf_ptr += sizeof(to->id); + } + + memcpy(buf_ptr, &from->id, sizeof(from->id)); + buf_ptr += sizeof(from->id); + + } + + /* TODO: if this copy turns out to be a performance concern, change sptps_send_record() to add some "pre-padding" to the buffer and use that instead */ + memcpy(buf_ptr, data, len); + buf_ptr += len; + + const sockaddr_t *sa = NULL; + int sock; + + if(relay->status.send_locally) { + choose_local_address(relay, &sa, &sock); + } + + if(!sa) { + choose_udp_address(relay, &sa, &sock); + } + + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet from %s (%s) to %s (%s) via %s (%s) (UDP)", from->name, from->hostname, to->name, to->hostname, relay->name, relay->hostname); + + if(sendto(listen_socket[sock].udp.fd, buf, buf_ptr - buf, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) { + if(sockmsgsize(sockerrno)) { + // Compensate for SPTPS overhead + len -= SPTPS_DATAGRAM_OVERHEAD; + + if(relay->maxmtu >= len) { + relay->maxmtu = len - 1; + } + + if(relay->mtu >= len) { + relay->mtu = len - 1; + } + + try_fix_mtu(relay); + } else { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", relay->name, relay->hostname, sockstrerror(sockerrno)); + return false; + } + } + + return true; +} + +bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) { + node_t *from = handle; + + if(type == SPTPS_HANDSHAKE) { + if(!from->status.validkey) { + from->status.validkey = true; + from->status.waitingforkey = false; + logger(DEBUG_META, LOG_INFO, "SPTPS key exchange with %s (%s) successful", from->name, from->hostname); + } + + return true; + } + + if(len > MTU) { + logger(DEBUG_ALWAYS, LOG_ERR, "Packet from %s (%s) larger than maximum supported size (%d > %d)", from->name, from->hostname, len, MTU); + return false; + } + + vpn_packet_t inpkt; + inpkt.offset = DEFAULT_PACKET_OFFSET; + inpkt.priority = 0; + + if(type == PKT_PROBE) { + if(!from->status.udppacket) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got SPTPS PROBE packet from %s (%s) via TCP", from->name, from->hostname); + return false; + } + + inpkt.len = len; + memcpy(DATA(&inpkt), data, len); + + if(inpkt.len > from->maxrecentlen) { + from->maxrecentlen = inpkt.len; + } + + udp_probe_h(from, &inpkt, len); + return true; + } + + if(type & ~(PKT_COMPRESSED | PKT_MAC)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname); + return false; + } + + /* Check if we have the headers we need */ + if(routing_mode != RMODE_ROUTER && !(type & PKT_MAC)) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Received packet from %s (%s) without MAC header (maybe Mode is not set correctly)", from->name, from->hostname); + return false; + } else if(routing_mode == RMODE_ROUTER && (type & PKT_MAC)) { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Received packet from %s (%s) with MAC header (maybe Mode is not set correctly)", from->name, from->hostname); + } + + int offset = (type & PKT_MAC) ? 0 : 14; + + if(type & PKT_COMPRESSED) { + length_t ulen = uncompress_packet(DATA(&inpkt) + offset, (const uint8_t *)data, len, from->incompression); + + if(!ulen) { + return false; + } else { + inpkt.len = ulen + offset; + } + + if(inpkt.len > MAXSIZE) { + abort(); + } + } else { + memcpy(DATA(&inpkt) + offset, data, len); + inpkt.len = len + offset; + } + + /* Generate the Ethernet packet type if necessary */ + if(offset) { + switch(DATA(&inpkt)[14] >> 4) { + case 4: + DATA(&inpkt)[12] = 0x08; + DATA(&inpkt)[13] = 0x00; + break; + + case 6: + DATA(&inpkt)[12] = 0x86; + DATA(&inpkt)[13] = 0xDD; + break; + + default: + logger(DEBUG_TRAFFIC, LOG_ERR, + "Unknown IP version %d while reading packet from %s (%s)", + DATA(&inpkt)[14] >> 4, from->name, from->hostname); + return false; + } + } + + if(from->status.udppacket && inpkt.len > from->maxrecentlen) { + from->maxrecentlen = inpkt.len; + } + + receive_packet(from, &inpkt); + return true; +} + +// This function tries to get SPTPS keys, if they aren't already known. +// This function makes no guarantees - it is up to the caller to check the node's state to figure out if the keys are available. +static void try_sptps(node_t *n) { + if(n->status.validkey) { + return; + } + + logger(DEBUG_TRAFFIC, LOG_INFO, "No valid key known yet for %s (%s)", n->name, n->hostname); + + if(!n->status.waitingforkey) { + send_req_key(n); + } else if(n->last_req_key + 10 < now.tv_sec) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "No key from %s after 10 seconds, restarting SPTPS", n->name); + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + send_req_key(n); + } + + return; +} + +static void send_udp_probe_packet(node_t *n, int len) { + vpn_packet_t packet; + packet.offset = DEFAULT_PACKET_OFFSET; + memset(DATA(&packet), 0, 14); + randomize(DATA(&packet) + 14, len - 14); + packet.len = len; + packet.priority = 0; + + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending UDP probe length %d to %s (%s)", len, n->name, n->hostname); + + send_udppacket(n, &packet); +} + +// This function tries to establish a UDP tunnel to a node so that packets can be sent. +// If a tunnel is already established, it makes sure it stays up. +// This function makes no guarantees - it is up to the caller to check the node's state to figure out if UDP is usable. +static void try_udp(node_t *n) { + if(!udp_discovery) { + return; + } + + /* Send gratuitous probe replies to 1.1 nodes. */ + + if((n->options >> 24) >= 3 && n->status.udp_confirmed) { + struct timeval ping_tx_elapsed; + timersub(&now, &n->udp_reply_sent, &ping_tx_elapsed); + + if(ping_tx_elapsed.tv_sec >= udp_discovery_keepalive_interval - 1) { + n->udp_reply_sent = now; + + if(n->maxrecentlen) { + vpn_packet_t pkt; + pkt.len = n->maxrecentlen; + pkt.offset = DEFAULT_PACKET_OFFSET; + memset(DATA(&pkt), 0, 14); + randomize(DATA(&pkt) + 14, MIN_PROBE_SIZE - 14); + send_udp_probe_reply(n, &pkt, pkt.len); + n->maxrecentlen = 0; + } + } + } + + /* Probe request */ + + struct timeval ping_tx_elapsed; + timersub(&now, &n->udp_ping_sent, &ping_tx_elapsed); + + int interval = n->status.udp_confirmed ? udp_discovery_keepalive_interval : udp_discovery_interval; + + if(ping_tx_elapsed.tv_sec >= interval) { + gettimeofday(&now, NULL); + n->udp_ping_sent = now; // a probe in flight + n->status.ping_sent = true; + send_udp_probe_packet(n, MIN_PROBE_SIZE); + + if(localdiscovery && !n->status.udp_confirmed && n->prevedge) { + n->status.send_locally = true; + send_udp_probe_packet(n, MIN_PROBE_SIZE); + n->status.send_locally = false; + } + } +} + +static length_t choose_initial_maxmtu(node_t *n) { +#ifdef IP_MTU + + int sock = -1; + + const sockaddr_t *sa = NULL; + int sockindex; + choose_udp_address(n, &sa, &sockindex); + + if(!sa) { + return MTU; + } + + sock = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); + + if(sock < 0) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Creating MTU assessment socket for %s (%s) failed: %s", n->name, n->hostname, sockstrerror(sockerrno)); + return MTU; + } + + if(connect(sock, &sa->sa, SALEN(sa->sa))) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Connecting MTU assessment socket for %s (%s) failed: %s", n->name, n->hostname, sockstrerror(sockerrno)); + close(sock); + return MTU; + } + + int ip_mtu; + socklen_t ip_mtu_len = sizeof(ip_mtu); + + if(getsockopt(sock, IPPROTO_IP, IP_MTU, &ip_mtu, &ip_mtu_len)) { + logger(DEBUG_TRAFFIC, LOG_ERR, "getsockopt(IP_MTU) on %s (%s) failed: %s", n->name, n->hostname, sockstrerror(sockerrno)); + close(sock); + return MTU; + } + + close(sock); + + /* getsockopt(IP_MTU) returns the MTU of the physical interface. + We need to remove various overheads to get to the tinc MTU. */ + length_t mtu = ip_mtu; + mtu -= (sa->sa.sa_family == AF_INET6) ? sizeof(struct ip6_hdr) : sizeof(struct ip); + mtu -= 8; /* UDP */ + + if(n->status.sptps) { + mtu -= SPTPS_DATAGRAM_OVERHEAD; + + if((n->options >> 24) >= 4) { + mtu -= sizeof(node_id_t) + sizeof(node_id_t); + } + +#ifndef DISABLE_LEGACY + } else { + mtu -= digest_length(n->outdigest); + + /* Now it's tricky. We use CBC mode, so the length of the + encrypted payload must be a multiple of the blocksize. The + sequence number is also part of the encrypted payload, so we + must account for it after correcting for the blocksize. + Furthermore, the padding in the last block must be at least + 1 byte. */ + + length_t blocksize = cipher_blocksize(n->outcipher); + + if(blocksize > 1) { + mtu /= blocksize; + mtu *= blocksize; + mtu--; + } + + mtu -= 4; // seqno +#endif + } + + if(mtu < 512) { + logger(DEBUG_TRAFFIC, LOG_ERR, "getsockopt(IP_MTU) on %s (%s) returned absurdly small value: %d", n->name, n->hostname, ip_mtu); + return MTU; + } + + if(mtu > MTU) { + return MTU; + } + + logger(DEBUG_TRAFFIC, LOG_INFO, "Using system-provided maximum tinc MTU for %s (%s): %hd", n->name, n->hostname, mtu); + return mtu; + +#else + (void)n; + return MTU; +#endif +} + +/* This function tries to determines the MTU of a node. + By calling this function repeatedly, n->minmtu will be progressively + increased, and at some point, n->mtu will be fixed to n->minmtu. If the MTU + is already fixed, this function checks if it can be increased. */ -void send_packet(const node_t *n, vpn_packet_t *packet) { - node_t *via; + +static void try_mtu(node_t *n) { + if(!(n->options & OPTION_PMTU_DISCOVERY)) { + return; + } + + if(udp_discovery && !n->status.udp_confirmed) { + n->maxrecentlen = 0; + n->mtuprobes = 0; + n->minmtu = 0; + n->maxmtu = MTU; + return; + } + + /* mtuprobes == 0..19: initial discovery, send bursts with 1 second interval, mtuprobes++ + mtuprobes == 20: fix MTU, and go to -1 + mtuprobes == -1: send one maxmtu and one maxmtu+1 probe every pinginterval + mtuprobes ==-2..-3: send one maxmtu probe every second + mtuprobes == -4: maxmtu no longer valid, reset minmtu and maxmtu and go to 0 */ + + struct timeval elapsed; + timersub(&now, &n->mtu_ping_sent, &elapsed); + + if(n->mtuprobes >= 0) { + if(n->mtuprobes != 0 && elapsed.tv_sec == 0 && elapsed.tv_usec < 333333) { + return; + } + } else { + if(n->mtuprobes < -1) { + if(elapsed.tv_sec < 1) { + return; + } + } else { + if(elapsed.tv_sec < pinginterval) { + return; + } + } + } + + n->mtu_ping_sent = now; + + try_fix_mtu(n); + + if(n->mtuprobes < -3) { + /* We lost three MTU probes, restart discovery */ + logger(DEBUG_TRAFFIC, LOG_INFO, "Decrease in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname); + n->mtuprobes = 0; + n->minmtu = 0; + } + + if(n->mtuprobes < 0) { + /* After the initial discovery, we only send one maxmtu and one + maxmtu+1 probe to detect PMTU increases. */ + send_udp_probe_packet(n, n->maxmtu); + + if(n->mtuprobes == -1 && n->maxmtu + 1 < MTU) { + send_udp_probe_packet(n, n->maxmtu + 1); + } + + n->mtuprobes--; + } else { + /* Before initial discovery begins, set maxmtu to the most likely value. + If it's underestimated, we will correct it after initial discovery. */ + if(n->mtuprobes == 0) { + n->maxmtu = choose_initial_maxmtu(n); + } + + for(;;) { + /* Decreasing the number of probes per cycle might make the algorithm react faster to lost packets, + but it will typically increase convergence time in the no-loss case. */ + const length_t probes_per_cycle = 8; + + /* This magic value was determined using math simulations. + It will result in a 1329-byte first probe, followed (if there was a reply) by a 1407-byte probe. + Since 1407 is just below the range of tinc MTUs over typical networks, + this fine-tuning allows tinc to cover a lot of ground very quickly. + This fine-tuning is only valid for maxmtu = MTU; if maxmtu is smaller, + then it's better to use a multiplier of 1. Indeed, this leads to an interesting scenario + if choose_initial_maxmtu() returns the actual MTU value - it will get confirmed with one single probe. */ + const float multiplier = (n->maxmtu == MTU) ? 0.97 : 1; + + const float cycle_position = probes_per_cycle - (n->mtuprobes % probes_per_cycle) - 1; + const length_t minmtu = MAX(n->minmtu, 512); + const float interval = n->maxmtu - minmtu; + + /* The core of the discovery algorithm is this exponential. + It produces very large probes early in the cycle, and then it very quickly decreases the probe size. + This reflects the fact that in the most difficult cases, we don't get any feedback for probes that + are too large, and therefore we need to concentrate on small offsets so that we can quickly converge + on the precise MTU as we are approaching it. + The last probe of the cycle is always 1 byte in size - this is to make sure we'll get at least one + reply per cycle so that we can make progress. */ + const length_t offset = powf(interval, multiplier * cycle_position / (probes_per_cycle - 1)); + + length_t maxmtu = n->maxmtu; + send_udp_probe_packet(n, minmtu + offset); + + /* If maxmtu changed, it means the probe was rejected by the system because it was too large. + In that case, we recalculate with the new maxmtu and try again. */ + if(n->mtuprobes < 0 || maxmtu == n->maxmtu) { + break; + } + } + + if(n->mtuprobes >= 0) { + n->mtuprobes++; + } + } +} + +/* These functions try to establish a tunnel to a node (or its relay) so that + packets can be sent (e.g. exchange keys). + If a tunnel is already established, it tries to improve it (e.g. by trying + to establish a UDP tunnel instead of TCP). This function makes no + guarantees - it is up to the caller to check the node's state to figure out + if TCP and/or UDP is usable. By calling this function repeatedly, the + tunnel is gradually improved until we hit the wall imposed by the underlying + network environment. It is recommended to call this function every time a + packet is sent (or intended to be sent) to a node, so that the tunnel keeps + improving as packets flow, and then gracefully downgrades itself as it goes + idle. +*/ + +static void try_tx_sptps(node_t *n, bool mtu) { + /* If n is a TCP-only neighbor, we'll only use "cleartext" PACKET + messages anyway, so there's no need for SPTPS at all. */ + + if(n->connection && ((myself->options | n->options) & OPTION_TCPONLY)) { + return; + } + + /* Otherwise, try to do SPTPS authentication with n if necessary. */ + + try_sptps(n); + + /* Do we need to statically relay packets? */ + + node_t *via = (n->via == myself) ? n->nexthop : n->via; + + /* If we do have a static relay, try everything with that one instead, if it supports relaying. */ + + if(via != n) { + if((via->options >> 24) < 4) { + return; + } + + try_tx(via, mtu); + return; + } + + /* Otherwise, try to establish UDP connectivity. */ + + try_udp(n); + + if(mtu) { + try_mtu(n); + } + + /* If we don't have UDP connectivity (yet), we need to use a dynamic relay (nexthop) + while we try to establish direct connectivity. */ + + if(!n->status.udp_confirmed && n != n->nexthop && (n->nexthop->options >> 24) >= 4) { + try_tx(n->nexthop, mtu); + } +} + +static void try_tx_legacy(node_t *n, bool mtu) { + /* Does he have our key? If not, send one. */ + + if(!n->status.validkey_in) { + send_ans_key(n); + } + + /* Check if we already have a key, or request one. */ + + if(!n->status.validkey) { + if(n->last_req_key + 10 <= now.tv_sec) { + send_req_key(n); + n->last_req_key = now.tv_sec; + } + + return; + } + + try_udp(n); + + if(mtu) { + try_mtu(n); + } +} + +void try_tx(node_t *n, bool mtu) { + if(!n->status.reachable) { + return; + } + + if(n->status.sptps) { + try_tx_sptps(n, mtu); + } else { + try_tx_legacy(n, mtu); + } +} + +void send_packet(node_t *n, vpn_packet_t *packet) { + // If it's for myself, write it to the tun/tap device. if(n == myself) { if(overwrite_mac) { - memcpy(packet->data, mymac.x, ETH_ALEN); + memcpy(DATA(packet), mymac.x, ETH_ALEN); + // Use an arbitrary fake source address. + memcpy(DATA(packet) + ETH_ALEN, DATA(packet), ETH_ALEN); + DATA(packet)[ETH_ALEN * 2 - 1] ^= 0xFF; } + n->out_packets++; + n->out_bytes += packet->len; devops.write(packet); return; } - ifdebug(TRAFFIC) logger(LOG_ERR, "Sending packet of %d bytes to %s (%s)", - packet->len, n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_ERR, "Sending packet of %d bytes to %s (%s)", packet->len, n->name, n->hostname); + + // If the node is not reachable, drop it. if(!n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Node %s (%s) is not reachable", - n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Node %s (%s) is not reachable", n->name, n->hostname); return; } - via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via; + // Keep track of packet statistics. - if(via != n) - ifdebug(TRAFFIC) logger(LOG_INFO, "Sending packet to %s via %s (%s)", - n->name, via->name, n->via->hostname); + n->out_packets++; + n->out_bytes += packet->len; + + // Check if it should be sent as an SPTPS packet. + + if(n->status.sptps) { + send_sptps_packet(n, packet); + try_tx(n, true); + return; + } + + // Determine which node to actually send it to. + + node_t *via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via; + + if(via != n) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet to %s via %s (%s)", n->name, via->name, n->via->hostname); + } + + // Try to send via UDP, unless TCP is forced. if(packet->priority == -1 || ((myself->options | via->options) & OPTION_TCPONLY)) { if(!send_tcppacket(via->connection, packet)) { terminate_connection(via->connection, true); } - } else { - send_udppacket(via, packet); + + return; } + + send_udppacket(via, packet); + try_tx(via, true); } -/* Broadcast a packet using the minimum spanning tree */ - void broadcast_packet(const node_t *from, vpn_packet_t *packet) { - avl_node_t *node; - connection_t *c; - node_t *n; - // Always give ourself a copy of the packet. if(from != myself) { send_packet(myself, packet); @@ -683,21 +1518,18 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) { return; } - ifdebug(TRAFFIC) logger(LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", - packet->len, from->name, from->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", + packet->len, from->name, from->hostname); switch(broadcast_mode) { // In MST mode, broadcast packets travel via the Minimum Spanning Tree. // This guarantees all nodes receive the broadcast packet, and // usually distributes the sending of broadcast packets over all nodes. case BMODE_MST: - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - - if(c->status.active && c->status.mst && c != from->nexthop->connection) { + for list_each(connection_t, c, connection_list) + if(c->edge && c->status.mst && c != from->nexthop->connection) { send_packet(c->node, packet); } - } break; @@ -709,13 +1541,10 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) { break; } - for(node = node_udp_tree->head; node; node = node->next) { - n = node->data; - + for splay_each(node_t, n, node_tree) if(n->status.reachable && n != myself && ((n->via == myself && n->nexthop == n) || n->via == n)) { send_packet(n, packet); } - } break; @@ -724,75 +1553,271 @@ void broadcast_packet(const node_t *from, vpn_packet_t *packet) { } } +/* We got a packet from some IP address, but we don't know who sent it. Try to + verify the message authentication code against all active session keys. + Since this is actually an expensive operation, we only do a full check once + a minute, the rest of the time we only check against nodes for which we know + an IP address that matches the one from the packet. */ + static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) { - avl_node_t *node; - edge_t *e; - node_t *n = NULL; + node_t *match = NULL; + bool hard = false; static time_t last_hard_try = 0; - for(node = edge_weight_tree->head; node; node = node->next) { - e = node->data; - - if(e->to == myself) { + for splay_each(node_t, n, node_tree) { + if(!n->status.reachable || n == myself) { continue; } - if(last_hard_try == now && sockaddrcmp_noport(from, &e->address)) { + if(!n->status.validkey_in && !(n->status.sptps && n->sptps.instate)) { continue; } - if(!try_mac(e->to, pkt)) { + bool soft = false; + + for splay_each(edge_t, e, n->edge_tree) { + if(!e->reverse) { + continue; + } + + if(!sockaddrcmp_noport(from, &e->reverse->address)) { + soft = true; + break; + } + } + + if(!soft) { + if(last_hard_try == now.tv_sec) { + continue; + } + + hard = true; + } + + if(!try_mac(n, pkt)) { continue; } - n = e->to; + match = n; break; } - last_hard_try = now; - return n; + if(hard) { + last_hard_try = now.tv_sec; + } + + return match; } -void handle_incoming_vpn_data(int sock) { - vpn_packet_t pkt; +static void handle_incoming_vpn_packet(listen_socket_t *ls, vpn_packet_t *pkt, sockaddr_t *addr) { char *hostname; - sockaddr_t from; - socklen_t fromlen = sizeof(from); - node_t *n; + node_id_t nullid = {0}; + node_t *from, *to; + bool direct = false; - ssize_t len = recvfrom(listen_socket[sock].udp, (char *) &pkt.seqno, MAXSIZE, 0, &from.sa, &fromlen); + sockaddrunmap(addr); /* Some braindead IPv6 implementations do stupid things. */ - if(len <= 0 || len > UINT16_MAX) { - if(len >= 0) { - logger(LOG_ERR, "Receiving packet with invalid size"); - } else if(!sockwouldblock(sockerrno)) { - logger(LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); + // Try to figure out who sent this packet. + + node_t *n = lookup_node_udp(addr); + + if(n && !n->status.udp_confirmed) { + n = NULL; // Don't believe it if we don't have confirmation yet. + } + + if(!n) { + // It might be from a 1.1 node, which might have a source ID in the packet. + pkt->offset = 2 * sizeof(node_id_t); + from = lookup_node_id(SRCID(pkt)); + + if(from && !memcmp(DSTID(pkt), &nullid, sizeof(nullid)) && from->status.sptps) { + if(sptps_verify_datagram(&from->sptps, DATA(pkt), pkt->len - 2 * sizeof(node_id_t))) { + n = from; + } else { + goto skip_harder; + } + } + } + + if(!n) { + pkt->offset = 0; + n = try_harder(addr, pkt); + } + +skip_harder: + + if(!n) { + if(debug_level >= DEBUG_PROTOCOL) { + hostname = sockaddr2hostname(addr); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname); + free(hostname); + } + + return; + } + + pkt->offset = 0; + + if(n->status.sptps) { + bool relay_enabled = (n->options >> 24) >= 4; + + if(relay_enabled) { + pkt->offset = 2 * sizeof(node_id_t); + pkt->len -= pkt->offset; + } + + if(!memcmp(DSTID(pkt), &nullid, sizeof(nullid)) || !relay_enabled) { + direct = true; + from = n; + to = myself; + } else { + from = lookup_node_id(SRCID(pkt)); + to = lookup_node_id(DSTID(pkt)); + } + + if(!from || !to) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from %s (%s) with unknown source and/or destination ID", n->name, n->hostname); + return; + } + + if(!to->status.reachable) { + /* This can happen in the form of a race condition + if the node just became unreachable. */ + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot relay packet from %s (%s) because the destination, %s (%s), is unreachable", from->name, from->hostname, to->name, to->hostname); + return; + } + + /* The packet is supposed to come from the originator or its static relay + (i.e. with no dynamic relays in between). + If it did not, "help" the static relay by sending it UDP info. + Note that we only do this if we're the destination or the static relay; + otherwise every hop would initiate its own UDP info message, resulting in elevated chatter. */ + + if(n != from->via && to->via == myself) { + send_udp_info(myself, from); + } + + /* If we're not the final recipient, relay the packet. */ + + if(to != myself) { + send_sptps_data(to, from, 0, DATA(pkt), pkt->len); + try_tx(to, true); + return; + } + } else { + direct = true; + from = n; + } + + if(!receive_udppacket(from, pkt)) { + return; + } + + n->sock = ls - listen_socket; + + if(direct && sockaddrcmp(addr, &n->address)) { + update_node_udp(n, addr); + } + + /* If the packet went through a relay, help the sender find the appropriate MTU + through the relay path. */ + + if(!direct) { + send_mtu_info(myself, n, MTU); + } +} + +void handle_incoming_vpn_data(void *data, int flags) { + (void)data; + (void)flags; + listen_socket_t *ls = data; + +#ifdef HAVE_RECVMMSG +#define MAX_MSG 64 + static int num = MAX_MSG; + static vpn_packet_t pkt[MAX_MSG]; + static sockaddr_t addr[MAX_MSG]; + static struct mmsghdr msg[MAX_MSG]; + static struct iovec iov[MAX_MSG]; + + for(int i = 0; i < num; i++) { + pkt[i].offset = 0; + + iov[i] = (struct iovec) { + .iov_base = DATA(&pkt[i]), + .iov_len = MAXSIZE, + }; + + msg[i].msg_hdr = (struct msghdr) { + .msg_name = &addr[i].sa, + .msg_namelen = sizeof(addr)[i], + .msg_iov = &iov[i], + .msg_iovlen = 1, + }; + } + + num = recvmmsg(ls->udp.fd, msg, MAX_MSG, MSG_DONTWAIT, NULL); + + if(num < 0) { + if(!sockwouldblock(sockerrno)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); + } + + return; + } + + for(int i = 0; i < num; i++) { + pkt[i].len = msg[i].msg_len; + + if(pkt[i].len <= 0 || pkt[i].len > MAXSIZE) { + continue; + } + + handle_incoming_vpn_packet(ls, &pkt[i], &addr[i]); + } + +#else + vpn_packet_t pkt; + sockaddr_t addr = {0}; + socklen_t addrlen = sizeof(addr); + + pkt.offset = 0; + int len = recvfrom(ls->udp.fd, (void *)DATA(&pkt), MAXSIZE, 0, &addr.sa, &addrlen); + + if(len <= 0 || (size_t)len > MAXSIZE) { + if(!sockwouldblock(sockerrno)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); } return; } pkt.len = len; - sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */ - n = lookup_node_udp(&from); + handle_incoming_vpn_packet(ls, &pkt, &addr); +#endif +} - if(!n) { - n = try_harder(&from, &pkt); +void handle_device_data(void *data, int flags) { + (void)data; + (void)flags; + vpn_packet_t packet; + packet.offset = DEFAULT_PACKET_OFFSET; + packet.priority = 0; + static int errors = 0; - if(n) { - update_node_udp(n, &from); - } else ifdebug(PROTOCOL) { - hostname = sockaddr2hostname(&from); - logger(LOG_WARNING, "Received UDP packet from unknown source %s", hostname); - free(hostname); - return; - } else { - return; + if(devops.read(&packet)) { + errors = 0; + myself->in_packets++; + myself->in_bytes += packet.len; + route(myself, &packet); + } else { + usleep(errors * 50000); + errors++; + + if(errors > 10) { + logger(DEBUG_ALWAYS, LOG_ERR, "Too many errors from %s, exiting!", device); + event_exit(); } } - - n->sock = sock; - - receive_udppacket(n, &pkt); } diff --git a/src/net_setup.c b/src/net_setup.c index f26007b..82f9bbc 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -1,7 +1,7 @@ /* net_setup.c -- Setup. Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2017 Guus Sliepen + 2000-2021 Guus Sliepen 2006 Scott Lamb 2010 Brandon Black @@ -22,270 +22,355 @@ #include "system.h" -#include -#include -#include -#include -#include -#include - -#include "avl_tree.h" +#include "cipher.h" #include "conf.h" #include "connection.h" +#include "control.h" #include "device.h" -#include "event.h" +#include "digest.h" +#include "ecdsa.h" #include "graph.h" #include "logger.h" +#include "names.h" #include "net.h" #include "netutl.h" #include "process.h" #include "protocol.h" -#include "proxy.h" #include "route.h" +#include "rsa.h" +#include "script.h" #include "subnet.h" #include "utils.h" #include "xalloc.h" -char *myport; -devops_t devops; - -#ifndef HAVE_RSA_SET0_KEY -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { - BN_free(r->n); - r->n = n; - BN_free(r->e); - r->e = e; - BN_free(r->d); - r->d = d; - return 1; -} +#ifdef HAVE_MINIUPNPC +#include "upnp.h" #endif -bool read_rsa_public_key(connection_t *c) { +char *myport; +static io_t device_io; +devops_t devops; +bool device_standby = false; + +char *proxyhost; +char *proxyport; +char *proxyuser; +char *proxypass; +proxytype_t proxytype; +bool autoconnect; +bool disablebuggypeers; + +char *scriptinterpreter; +char *scriptextension; + +bool node_read_ecdsa_public_key(node_t *n) { + if(ecdsa_active(n->ecdsa)) { + return true; + } + + splay_tree_t *config_tree; FILE *fp; - char *pubname; - char *hcfname; - char *key; - BIGNUM *n = NULL; - BIGNUM *e = NULL; + char *pubname = NULL; + char *p; - if(!c->rsa_key) { - c->rsa_key = RSA_new(); -// RSA_blinding_on(c->rsa_key, NULL); + init_configuration(&config_tree); + + if(!read_host_config(config_tree, n->name, true)) { + goto exit; } - /* First, check for simple PublicKey statement */ + /* First, check for simple Ed25519PublicKey statement */ - if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) { - if((size_t)BN_hex2bn(&n, key) != strlen(key)) { - free(key); - logger(LOG_ERR, "Invalid PublicKey for %s!", c->name); - return false; - } - - free(key); - BN_hex2bn(&e, "FFFF"); - - if(!n || !e || RSA_set0_key(c->rsa_key, n, e, NULL) != 1) { - BN_free(e); - BN_free(n); - logger(LOG_ERR, "RSA_set0_key() failed with PublicKey for %s!", c->name); - return false; - } - - return true; + if(get_config_string(lookup_config(config_tree, "Ed25519PublicKey"), &p)) { + n->ecdsa = ecdsa_set_base64_public_key(p); + free(p); + goto exit; } - /* Else, check for PublicKeyFile statement and read it */ + /* Else, check for Ed25519PublicKeyFile statement and read it */ - if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &pubname)) { - fp = fopen(pubname, "r"); - - if(!fp) { - logger(LOG_ERR, "Error reading RSA public key file `%s': %s", pubname, strerror(errno)); - free(pubname); - return false; - } - - c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL); - fclose(fp); - - if(c->rsa_key) { - free(pubname); - return true; /* Woohoo. */ - } - - /* If it fails, try PEM_read_RSA_PUBKEY. */ - fp = fopen(pubname, "r"); - - if(!fp) { - logger(LOG_ERR, "Error reading RSA public key file `%s': %s", pubname, strerror(errno)); - free(pubname); - return false; - } - - c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL); - fclose(fp); - - if(c->rsa_key) { -// RSA_blinding_on(c->rsa_key, NULL); - free(pubname); - return true; - } - - logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", pubname, strerror(errno)); - free(pubname); - return false; + if(!get_config_string(lookup_config(config_tree, "Ed25519PublicKeyFile"), &pubname)) { + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, n->name); } - /* Else, check if a harnessed public key is in the config file */ - - xasprintf(&hcfname, "%s/hosts/%s", confbase, c->name); - fp = fopen(hcfname, "r"); + fp = fopen(pubname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading RSA public key file `%s': %s", hcfname, strerror(errno)); - free(hcfname); - return false; + goto exit; } - c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL); + n->ecdsa = ecdsa_read_pem_public_key(fp); fclose(fp); - if(c->rsa_key) { - free(hcfname); - return true; - } - - /* Try again with PEM_read_RSA_PUBKEY. */ - - fp = fopen(hcfname, "r"); - - if(!fp) { - logger(LOG_ERR, "Error reading RSA public key file `%s': %s", hcfname, strerror(errno)); - free(hcfname); - return false; - } - - free(hcfname); - c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL); -// RSA_blinding_on(c->rsa_key, NULL); - fclose(fp); - - if(c->rsa_key) { - return true; - } - - logger(LOG_ERR, "No public key for %s specified!", c->name); - - return false; +exit: + exit_configuration(&config_tree); + free(pubname); + return n->ecdsa; } -static bool read_rsa_private_key(void) { - FILE *fp; - char *fname, *key, *pubkey; - BIGNUM *n = NULL; - BIGNUM *e = NULL; - BIGNUM *d = NULL; - - if(get_config_string(lookup_config(config_tree, "PrivateKey"), &key)) { - myself->connection->rsa_key = RSA_new(); - -// RSA_blinding_on(myself->connection->rsa_key, NULL); - if((size_t)BN_hex2bn(&d, key) != strlen(key)) { - logger(LOG_ERR, "Invalid PrivateKey for myself!"); - free(key); - return false; - } - - free(key); - - if(!get_config_string(lookup_config(config_tree, "PublicKey"), &pubkey)) { - BN_free(d); - logger(LOG_ERR, "PrivateKey used but no PublicKey found!"); - return false; - } - - if((size_t)BN_hex2bn(&n, pubkey) != strlen(pubkey)) { - free(pubkey); - BN_free(d); - logger(LOG_ERR, "Invalid PublicKey for myself!"); - return false; - } - - free(pubkey); - BN_hex2bn(&e, "FFFF"); - - if(!n || !e || !d || RSA_set0_key(myself->connection->rsa_key, n, e, d) != 1) { - BN_free(d); - BN_free(e); - BN_free(n); - logger(LOG_ERR, "RSA_set0_key() failed with PrivateKey for myself!"); - return false; - } - +bool read_ecdsa_public_key(connection_t *c) { + if(ecdsa_active(c->ecdsa)) { return true; } - if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname)) { - xasprintf(&fname, "%s/rsa_key.priv", confbase); + FILE *fp; + char *fname; + char *p; + + if(!c->config_tree) { + init_configuration(&c->config_tree); + + if(!read_host_config(c->config_tree, c->name, true)) { + return false; + } + } + + /* First, check for simple Ed25519PublicKey statement */ + + if(get_config_string(lookup_config(c->config_tree, "Ed25519PublicKey"), &p)) { + c->ecdsa = ecdsa_set_base64_public_key(p); + free(p); + return c->ecdsa; + } + + /* Else, check for Ed25519PublicKeyFile statement and read it */ + + if(!get_config_string(lookup_config(c->config_tree, "Ed25519PublicKeyFile"), &fname)) { + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); } fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading RSA private key file `%s': %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading Ed25519 public key file `%s': %s", fname, strerror(errno)); free(fname); return false; } -#if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN) + c->ecdsa = ecdsa_read_pem_public_key(fp); + + if(!c->ecdsa && errno != ENOENT) { + logger(DEBUG_ALWAYS, LOG_ERR, "Parsing Ed25519 public key file `%s' failed.", fname); + } + + fclose(fp); + free(fname); + return c->ecdsa; +} + +#ifndef DISABLE_LEGACY +bool read_rsa_public_key(connection_t *c) { + FILE *fp; + char *fname; + char *n; + + /* First, check for simple PublicKey statement */ + + if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &n)) { + c->rsa = rsa_set_hex_public_key(n, "FFFF"); + free(n); + return c->rsa; + } + + /* Else, check for PublicKeyFile statement and read it */ + + if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) { + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); + } + + fp = fopen(fname, "r"); + + if(!fp) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno)); + free(fname); + return false; + } + + c->rsa = rsa_read_pem_public_key(fp); + fclose(fp); + + if(!c->rsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno)); + } + + free(fname); + return c->rsa; +} +#endif + +static bool read_ecdsa_private_key(void) { + FILE *fp; + char *fname; + + /* Check for PrivateKeyFile statement and read it */ + + if(!get_config_string(lookup_config(config_tree, "Ed25519PrivateKeyFile"), &fname)) { + xasprintf(&fname, "%s" SLASH "ed25519_key.priv", confbase); + } + + fp = fopen(fname, "r"); + + if(!fp) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading Ed25519 private key file `%s': %s", fname, strerror(errno)); + + if(errno == ENOENT) { + logger(DEBUG_ALWAYS, LOG_INFO, "Create an Ed25519 key pair with `tinc -n %s generate-ed25519-keys'.", netname ? netname : "."); + } + + free(fname); + return false; + } + +#ifndef HAVE_MINGW struct stat s; - if(!fstat(fileno(fp), &s)) { - if(s.st_mode & ~0100700) { - logger(LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname); - } - } else { - logger(LOG_WARNING, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno)); + if(fstat(fileno(fp), &s)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat Ed25519 private key file `%s': %s'", fname, strerror(errno)); + free(fname); + return false; + } + + if(s.st_mode & ~0100700) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: insecure file permissions for Ed25519 private key file `%s'!", fname); } #endif - myself->connection->rsa_key = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); + myself->connection->ecdsa = ecdsa_read_pem_private_key(fp); fclose(fp); - if(!myself->connection->rsa_key) { - logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s", + if(!myself->connection->ecdsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Reading Ed25519 private key file `%s' failed", fname); + } + + free(fname); + return myself->connection->ecdsa; +} + +static bool read_invitation_key(void) { + FILE *fp; + char fname[PATH_MAX]; + + if(invitation_key) { + ecdsa_free(invitation_key); + invitation_key = NULL; + } + + snprintf(fname, sizeof(fname), "%s" SLASH "invitations" SLASH "ed25519_key.priv", confbase); + + fp = fopen(fname, "r"); + + if(fp) { + invitation_key = ecdsa_read_pem_private_key(fp); + fclose(fp); + + if(!invitation_key) { + logger(DEBUG_ALWAYS, LOG_ERR, "Reading Ed25519 private key file `%s' failed", fname); + } + } + + return invitation_key; +} + +#ifndef DISABLE_LEGACY +static bool read_rsa_private_key(void) { + FILE *fp; + char *fname; + char *n, *d; + + /* First, check for simple PrivateKey statement */ + + if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) { + if(!get_config_string(lookup_config(config_tree, "PublicKey"), &n)) { + logger(DEBUG_ALWAYS, LOG_ERR, "PrivateKey used but no PublicKey found!"); + free(d); + return false; + } + + myself->connection->rsa = rsa_set_hex_private_key(n, "FFFF", d); + free(n); + free(d); + return myself->connection->rsa; + } + + /* Else, check for PrivateKeyFile statement and read it */ + + if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname)) { + xasprintf(&fname, "%s" SLASH "rsa_key.priv", confbase); + } + + fp = fopen(fname, "r"); + + if(!fp) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading RSA private key file `%s': %s", fname, strerror(errno)); + + if(errno == ENOENT) { + logger(DEBUG_ALWAYS, LOG_INFO, "Create an RSA key pair with `tinc -n %s generate-rsa-keys'.", netname ? netname : "."); + } + free(fname); return false; } +#ifndef HAVE_MINGW + struct stat s; + + if(fstat(fileno(fp), &s)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno)); + free(fname); + return false; + } + + if(s.st_mode & ~0100700) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname); + } + +#endif + + myself->connection->rsa = rsa_read_pem_private_key(fp); + fclose(fp); + + if(!myself->connection->rsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Reading RSA private key file `%s' failed: %s", fname, strerror(errno)); + } + free(fname); - return true; + return myself->connection->rsa; +} +#endif + +#ifndef DISABLE_LEGACY +static timeout_t keyexpire_timeout; + +static void keyexpire_handler(void *data) { + regenerate_key(); + timeout_set(data, &(struct timeval) { + keylifetime, rand() % 100000 + }); +} +#endif + +void regenerate_key(void) { + logger(DEBUG_STATUS, LOG_INFO, "Expiring symmetric keys"); + send_key_changed(); + + for splay_each(node_t, n, node_tree) { + n->status.validkey_in = false; + } } -/* - Read Subnets from all host config files -*/ -void load_all_subnets(void) { +void load_all_nodes(void) { DIR *dir; struct dirent *ent; - char *dname; - char *fname; - avl_tree_t *config_tree; - config_t *cfg; - subnet_t *s, *s2; - node_t *n; + char dname[PATH_MAX]; - xasprintf(&dname, "%s/hosts", confbase); + snprintf(dname, sizeof(dname), "%s" SLASH "hosts", confbase); dir = opendir(dname); if(!dir) { - logger(LOG_ERR, "Could not open %s: %s", dname, strerror(errno)); - free(dname); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", dname, strerror(errno)); return; } @@ -294,17 +379,12 @@ void load_all_subnets(void) { continue; } - n = lookup_node(ent->d_name); -#ifdef _DIRENT_HAVE_D_TYPE - //if(ent->d_type != DT_REG) - // continue; -#endif + node_t *n = lookup_node(ent->d_name); - xasprintf(&fname, "%s/hosts/%s", confbase, ent->d_name); + splay_tree_t *config_tree; init_configuration(&config_tree); read_config_options(config_tree, ent->d_name); - read_config_file(config_tree, fname); - free(fname); + read_host_config(config_tree, ent->d_name, true); if(!n) { n = new_node(); @@ -312,18 +392,27 @@ void load_all_subnets(void) { node_add(n); } - for(cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { - if(!get_config_subnet(cfg, &s)) { - continue; - } + if(strictsubnets) { + for(config_t *cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + subnet_t *s, *s2; - if((s2 = lookup_subnet(n, s))) { - s2->expires = -1; - } else { - subnet_add(n, s); + if(!get_config_subnet(cfg, &s)) { + continue; + } + + if((s2 = lookup_subnet(n, s))) { + s2->expires = -1; + free(s); + } else { + subnet_add(n, s); + } } } + if(lookup_config(config_tree, "Address")) { + n->status.has_address = true; + } + exit_configuration(&config_tree); } @@ -332,6 +421,7 @@ void load_all_subnets(void) { char *get_name(void) { char *name = NULL; + char *returned_name; get_config_string(lookup_config(config_tree, "Name"), &name); @@ -339,112 +429,34 @@ char *get_name(void) { return NULL; } - if(*name == '$') { - char *envname = getenv(name + 1); - char hostname[32] = ""; - - if(!envname) { - if(strcmp(name + 1, "HOST")) { - fprintf(stderr, "Invalid Name: environment variable %s does not exist\n", name + 1); - free(name); - return false; - } - - if(gethostname(hostname, sizeof(hostname)) || !*hostname) { - fprintf(stderr, "Could not get hostname: %s\n", strerror(errno)); - free(name); - return false; - } - - hostname[31] = 0; - envname = hostname; - } - - free(name); - name = xstrdup(envname); - - for(char *c = name; *c; c++) - if(!isalnum(*c)) { - *c = '_'; - } - } - - if(!check_id(name)) { - logger(LOG_ERR, "Invalid name for myself!"); - free(name); - return false; - } - - return name; + returned_name = replace_name(name); + free(name); + return returned_name; } -/* - Configure node_t myself and set up the local sockets (listen only) -*/ -static bool setup_myself(void) { - config_t *cfg; - subnet_t *subnet; - char *name, *hostname, *mode, *afname, *cipher, *digest, *type; - char *fname = NULL; - char *address = NULL; +bool setup_myself_reloadable(void) { char *proxy = NULL; + char *rmode = NULL; + char *fmode = NULL; + char *bmode = NULL; + char *afname = NULL; char *space; - char *envp[5] = {0}; - struct addrinfo *ai, *aip, hint = {0}; bool choice; - int i, err; - int replaywin_int; - bool port_specified = false; - myself = new_node(); - myself->connection = new_connection(); + free(scriptinterpreter); + scriptinterpreter = NULL; + get_config_string(lookup_config(config_tree, "ScriptsInterpreter"), &scriptinterpreter); - myself->hostname = xstrdup("MYSELF"); - myself->connection->hostname = xstrdup("MYSELF"); - myself->connection->options = 0; - myself->connection->protocol_version = PROT_CURRENT; + free(scriptextension); - if(!(name = get_name())) { - logger(LOG_ERR, "Name for tinc daemon required!"); - return false; + if(!get_config_string(lookup_config(config_tree, "ScriptsExtension"), &scriptextension)) { + scriptextension = xstrdup(""); } - /* Read tinc.conf and our own host config file */ + get_config_string(lookup_config(config_tree, "Proxy"), &proxy); - myself->name = name; - myself->connection->name = xstrdup(name); - xasprintf(&fname, "%s/hosts/%s", confbase, name); - read_config_options(config_tree, name); - read_config_file(config_tree, fname); - free(fname); - - if(!read_rsa_private_key()) { - return false; - } - - if(!get_config_string(lookup_config(config_tree, "Port"), &myport)) { - myport = xstrdup("655"); - } else { - port_specified = true; - } - - /* Ensure myport is numeric */ - - if(!atoi(myport)) { - struct addrinfo *ai = str2addrinfo("localhost", myport, SOCK_DGRAM); - sockaddr_t sa; - - if(!ai || !ai->ai_addr) { - return false; - } - - free(myport); - memcpy(&sa, ai->ai_addr, ai->ai_addrlen); - sockaddr2str(&sa, NULL, &myport); - } - - if(get_config_string(lookup_config(config_tree, "Proxy"), &proxy)) { + if(proxy) { if((space = strchr(proxy, ' '))) { *space++ = 0; } @@ -462,8 +474,7 @@ static bool setup_myself(void) { } else if(!strcasecmp(proxy, "exec")) { proxytype = PROXY_EXEC; } else { - logger(LOG_ERR, "Unknown proxy type %s!", proxy); - free(proxy); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type %s!", proxy); return false; } @@ -474,8 +485,7 @@ static bool setup_myself(void) { case PROXY_EXEC: if(!space || !*space) { - logger(LOG_ERR, "Argument expected for proxy type exec!"); - free(proxy); + logger(DEBUG_ALWAYS, LOG_ERR, "Argument expected for proxy type exec!"); return false; } @@ -501,8 +511,7 @@ static bool setup_myself(void) { } if(!proxyhost || !*proxyhost || !proxyport || !*proxyport) { - logger(LOG_ERR, "Host and port argument expected for proxy!"); - free(proxy); + logger(DEBUG_ALWAYS, LOG_ERR, "Host and port argument expected for proxy!"); return false; } @@ -523,22 +532,6 @@ static bool setup_myself(void) { free(proxy); } - /* Read in all the subnets specified in the host configuration file */ - - cfg = lookup_config(config_tree, "Subnet"); - - while(cfg) { - if(!get_config_subnet(cfg, &subnet)) { - return false; - } - - subnet_add(myself, subnet); - - cfg = lookup_config_next(config_tree, cfg); - } - - /* Check some options */ - if(get_config_bool(lookup_config(config_tree, "IndirectData"), &choice) && choice) { myself->options |= OPTION_INDIRECT; } @@ -551,42 +544,45 @@ static bool setup_myself(void) { myself->options |= OPTION_INDIRECT; } - get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly); - get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets); - get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver); - get_config_bool(lookup_config(config_tree, "LocalDiscovery"), &localdiscovery); - strictsubnets |= tunnelserver; + get_config_bool(lookup_config(config_tree, "UDPDiscovery"), &udp_discovery); + get_config_int(lookup_config(config_tree, "UDPDiscoveryKeepaliveInterval"), &udp_discovery_keepalive_interval); + get_config_int(lookup_config(config_tree, "UDPDiscoveryInterval"), &udp_discovery_interval); + get_config_int(lookup_config(config_tree, "UDPDiscoveryTimeout"), &udp_discovery_timeout); - if(get_config_string(lookup_config(config_tree, "Mode"), &mode)) { - if(!strcasecmp(mode, "router")) { + get_config_int(lookup_config(config_tree, "MTUInfoInterval"), &mtu_info_interval); + get_config_int(lookup_config(config_tree, "UDPInfoInterval"), &udp_info_interval); + + get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly); + get_config_bool(lookup_config(config_tree, "LocalDiscovery"), &localdiscovery); + + if(get_config_string(lookup_config(config_tree, "Mode"), &rmode)) { + if(!strcasecmp(rmode, "router")) { routing_mode = RMODE_ROUTER; - } else if(!strcasecmp(mode, "switch")) { + } else if(!strcasecmp(rmode, "switch")) { routing_mode = RMODE_SWITCH; - } else if(!strcasecmp(mode, "hub")) { + } else if(!strcasecmp(rmode, "hub")) { routing_mode = RMODE_HUB; } else { - logger(LOG_ERR, "Invalid routing mode!"); - free(mode); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid routing mode!"); return false; } - free(mode); + free(rmode); } - if(get_config_string(lookup_config(config_tree, "Forwarding"), &mode)) { - if(!strcasecmp(mode, "off")) { + if(get_config_string(lookup_config(config_tree, "Forwarding"), &fmode)) { + if(!strcasecmp(fmode, "off")) { forwarding_mode = FMODE_OFF; - } else if(!strcasecmp(mode, "internal")) { + } else if(!strcasecmp(fmode, "internal")) { forwarding_mode = FMODE_INTERNAL; - } else if(!strcasecmp(mode, "kernel")) { + } else if(!strcasecmp(fmode, "kernel")) { forwarding_mode = FMODE_KERNEL; } else { - logger(LOG_ERR, "Invalid forwarding mode!"); - free(mode); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid forwarding mode!"); return false; } - free(mode); + free(fmode); } choice = !(myself->options & OPTION_TCPONLY); @@ -606,34 +602,55 @@ static bool setup_myself(void) { get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance); get_config_bool(lookup_config(config_tree, "DecrementTTL"), &decrement_ttl); - if(get_config_string(lookup_config(config_tree, "Broadcast"), &mode)) { - if(!strcasecmp(mode, "no")) { + if(get_config_string(lookup_config(config_tree, "Broadcast"), &bmode)) { + if(!strcasecmp(bmode, "no")) { broadcast_mode = BMODE_NONE; - } else if(!strcasecmp(mode, "yes") || !strcasecmp(mode, "mst")) { + } else if(!strcasecmp(bmode, "yes") || !strcasecmp(bmode, "mst")) { broadcast_mode = BMODE_MST; - } else if(!strcasecmp(mode, "direct")) { + } else if(!strcasecmp(bmode, "direct")) { broadcast_mode = BMODE_DIRECT; } else { - logger(LOG_ERR, "Invalid broadcast mode!"); - free(mode); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid broadcast mode!"); return false; } - free(mode); + free(bmode); } -#if !defined(SOL_IP) || !defined(IP_TOS) + const char *const DEFAULT_BROADCAST_SUBNETS[] = { "ff:ff:ff:ff:ff:ff", "255.255.255.255", "224.0.0.0/4", "ff00::/8" }; + + for(size_t i = 0; i < sizeof(DEFAULT_BROADCAST_SUBNETS) / sizeof(*DEFAULT_BROADCAST_SUBNETS); i++) { + subnet_t *s = new_subnet(); + + if(!str2net(s, DEFAULT_BROADCAST_SUBNETS[i])) { + abort(); + } + + subnet_add(NULL, s); + } + + for(config_t *cfg = lookup_config(config_tree, "BroadcastSubnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + subnet_t *s; + + if(!get_config_subnet(cfg, &s)) { + continue; + } + + subnet_add(NULL, s); + } + +#if !defined(IP_TOS) if(priorityinheritance) { - logger(LOG_WARNING, "%s not supported on this platform for IPv4 connection", "PriorityInheritance"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform for IPv4 connections", "PriorityInheritance"); } #endif -#if !defined(IPPROTO_IPV6) || !defined(IPV6_TCLASS) +#if !defined(IPV6_TCLASS) if(priorityinheritance) { - logger(LOG_WARNING, "%s not supported on this platform for IPv6 connection", "PriorityInheritance"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform for IPv6 connections", "PriorityInheritance"); } #endif @@ -644,50 +661,13 @@ static bool setup_myself(void) { if(get_config_int(lookup_config(config_tree, "MaxTimeout"), &maxtimeout)) { if(maxtimeout <= 0) { - logger(LOG_ERR, "Bogus maximum timeout!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus maximum timeout!"); return false; } } else { maxtimeout = 900; } - if(get_config_int(lookup_config(config_tree, "MinTimeout"), &mintimeout)) { - if(mintimeout < 0) { - logger(LOG_ERR, "Bogus minimum timeout!"); - return false; - } - - if(mintimeout > maxtimeout) { - logger(LOG_WARNING, "Minimum timeout (%d s) cannot be larger than maximum timeout (%d s). Correcting !", mintimeout, maxtimeout); - mintimeout = maxtimeout; - } - } else { - mintimeout = 0; - } - - if(get_config_int(lookup_config(config_tree, "UDPRcvBuf"), &udp_rcvbuf)) { - if(udp_rcvbuf <= 0) { - logger(LOG_ERR, "UDPRcvBuf cannot be negative!"); - return false; - } - } - - if(get_config_int(lookup_config(config_tree, "UDPSndBuf"), &udp_sndbuf)) { - if(udp_sndbuf <= 0) { - logger(LOG_ERR, "UDPSndBuf cannot be negative!"); - return false; - } - } - - if(get_config_int(lookup_config(config_tree, "ReplayWindow"), &replaywin_int)) { - if(replaywin_int < 0) { - logger(LOG_ERR, "ReplayWindow cannot be negative!"); - return false; - } - - replaywin = (unsigned)replaywin_int; - } - if(get_config_string(lookup_config(config_tree, "AddressFamily"), &afname)) { if(!strcasecmp(afname, "IPv4")) { addressfamily = AF_INET; @@ -696,8 +676,7 @@ static bool setup_myself(void) { } else if(!strcasecmp(afname, "any")) { addressfamily = AF_UNSPEC; } else { - logger(LOG_ERR, "Invalid address family!"); - free(afname); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid address family!"); return false; } @@ -706,99 +685,342 @@ static bool setup_myself(void) { get_config_bool(lookup_config(config_tree, "Hostnames"), &hostnames); - /* Generate packet encryption key */ - - if(get_config_string(lookup_config(config_tree, "Cipher"), &cipher)) { - if(!strcasecmp(cipher, "none")) { - myself->incipher = NULL; - } else { - myself->incipher = EVP_get_cipherbyname(cipher); - - if(!myself->incipher) { - logger(LOG_ERR, "Unrecognized cipher type!"); - free(cipher); - return false; - } - } - - free(cipher); - } else { - myself->incipher = EVP_aes_256_cbc(); - } - - if(myself->incipher) { - myself->inkeylength = EVP_CIPHER_key_length(myself->incipher) + EVP_CIPHER_iv_length(myself->incipher); - } else { - myself->inkeylength = 1; - } - - /* We need to use a stream mode for the meta protocol. Use AES for this, - but try to match the key size with the one from the cipher selected - by Cipher. - - If Cipher is set to none, still use a low level of encryption for the - meta protocol. - */ - - int keylen = myself->incipher ? EVP_CIPHER_key_length(myself->incipher) : 0; - - if(keylen <= 16) { - myself->connection->outcipher = EVP_aes_128_cfb(); - } else if(keylen <= 24) { - myself->connection->outcipher = EVP_aes_192_cfb(); - } else { - myself->connection->outcipher = EVP_aes_256_cfb(); - } - if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime)) { keylifetime = 3600; } - keyexpires = now + keylifetime; + if(!get_config_bool(lookup_config(config_tree, "AutoConnect"), &autoconnect)) { + autoconnect = true; + } + + get_config_bool(lookup_config(config_tree, "DisableBuggyPeers"), &disablebuggypeers); + + if(!get_config_int(lookup_config(config_tree, "InvitationExpire"), &invitation_lifetime)) { + invitation_lifetime = 604800; // 1 week + } + + read_invitation_key(); + + return true; +} + +/* + Add listening sockets. +*/ +static bool add_listen_address(char *address, bool bindto) { + char *port = myport; + + if(address) { + char *space = strchr(address, ' '); + + if(space) { + *space++ = 0; + port = space; + } + + if(!strcmp(address, "*")) { + *address = 0; + } + } + + struct addrinfo *ai, hint = {0}; + + hint.ai_family = addressfamily; + + hint.ai_socktype = SOCK_STREAM; + + hint.ai_protocol = IPPROTO_TCP; + + hint.ai_flags = AI_PASSIVE; + +#if HAVE_DECL_RES_INIT + res_init(); + +#endif + int err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai); + + free(address); + + if(err || !ai) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "getaddrinfo", err == EAI_SYSTEM ? strerror(err) : gai_strerror(err)); + return false; + } + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + // Ignore duplicate addresses + bool found = false; + + for(int i = 0; i < listen_sockets; i++) + if(!memcmp(&listen_socket[i].sa, aip->ai_addr, aip->ai_addrlen)) { + found = true; + break; + } + + if(found) { + continue; + } + + if(listen_sockets >= MAXSOCKETS) { + logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets"); + return false; + } + + int tcp_fd = setup_listen_socket((sockaddr_t *) aip->ai_addr); + + if(tcp_fd < 0) { + continue; + } + + int udp_fd = setup_vpn_in_socket((sockaddr_t *) aip->ai_addr); + + if(udp_fd < 0) { + close(tcp_fd); + continue; + } + + io_add(&listen_socket[listen_sockets].tcp, handle_new_meta_connection, &listen_socket[listen_sockets], tcp_fd, IO_READ); + io_add(&listen_socket[listen_sockets].udp, handle_incoming_vpn_data, &listen_socket[listen_sockets], udp_fd, IO_READ); + + if(debug_level >= DEBUG_CONNECTIONS) { + char *hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname); + free(hostname); + } + + listen_socket[listen_sockets].bindto = bindto; + memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); + listen_sockets++; + } + + freeaddrinfo(ai); + return true; +} + +void device_enable(void) { + if(devops.enable) { + devops.enable(); + } + + /* Run tinc-up script to further initialize the tap interface */ + + environment_t env; + environment_init(&env); + execute_script("tinc-up", &env); + environment_exit(&env); +} + +void device_disable(void) { + environment_t env; + environment_init(&env); + execute_script("tinc-down", &env); + environment_exit(&env); + + if(devops.disable) { + devops.disable(); + } +} + +/* + Configure node_t myself and set up the local sockets (listen only) +*/ +static bool setup_myself(void) { + char *name, *hostname, *type; + char *address = NULL; + bool port_specified = false; + + if(!(name = get_name())) { + logger(DEBUG_ALWAYS, LOG_ERR, "Name for tinc daemon required!"); + return false; + } + + myname = xstrdup(name); + myself = new_node(); + myself->connection = new_connection(); + myself->name = name; + myself->connection->name = xstrdup(name); + read_host_config(config_tree, name, true); + + if(!get_config_string(lookup_config(config_tree, "Port"), &myport)) { + myport = xstrdup("655"); + } else { + port_specified = true; + } + + myself->connection->options = 0; + myself->connection->protocol_major = PROT_MAJOR; + myself->connection->protocol_minor = PROT_MINOR; + + myself->options |= PROT_MINOR << 24; + +#ifdef DISABLE_LEGACY + experimental = read_ecdsa_private_key(); + + if(!experimental) { + logger(DEBUG_ALWAYS, LOG_ERR, "No private key available, cannot start tinc!"); + return false; + } + +#else + + if(!get_config_bool(lookup_config(config_tree, "ExperimentalProtocol"), &experimental)) { + experimental = read_ecdsa_private_key(); + + if(!experimental) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Support for SPTPS disabled."); + } + } else { + if(experimental && !read_ecdsa_private_key()) { + return false; + } + } + + if(!read_rsa_private_key()) { + if(experimental) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Support for legacy protocol disabled."); + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "No private keys available, cannot start tinc!"); + return false; + } + } + +#endif + + /* Ensure myport is numeric */ + + if(!atoi(myport)) { + struct addrinfo *ai = str2addrinfo("localhost", myport, SOCK_DGRAM); + sockaddr_t sa; + + if(!ai || !ai->ai_addr) { + return false; + } + + free(myport); + memcpy(&sa, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + sockaddr2str(&sa, NULL, &myport); + } + + /* Read in all the subnets specified in the host configuration file */ + + for(config_t *cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + subnet_t *subnet; + + if(!get_config_subnet(cfg, &subnet)) { + return false; + } + + subnet_add(myself, subnet); + } + + /* Check some options */ + + if(!setup_myself_reloadable()) { + return false; + } + + get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets); + get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver); + strictsubnets |= tunnelserver; + + if(get_config_int(lookup_config(config_tree, "MaxConnectionBurst"), &max_connection_burst)) { + if(max_connection_burst <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "MaxConnectionBurst cannot be negative!"); + return false; + } + } + + if(get_config_int(lookup_config(config_tree, "UDPRcvBuf"), &udp_rcvbuf)) { + if(udp_rcvbuf < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "UDPRcvBuf cannot be negative!"); + return false; + } + } + + if(get_config_int(lookup_config(config_tree, "UDPSndBuf"), &udp_sndbuf)) { + if(udp_sndbuf < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "UDPSndBuf cannot be negative!"); + return false; + } + } + + get_config_int(lookup_config(config_tree, "FWMark"), &fwmark); +#ifndef SO_MARK + + if(fwmark) { + logger(DEBUG_ALWAYS, LOG_ERR, "FWMark not supported on this platform!"); + return false; + } + +#endif + + int replaywin_int; + + if(get_config_int(lookup_config(config_tree, "ReplayWindow"), &replaywin_int)) { + if(replaywin_int < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "ReplayWindow cannot be negative!"); + return false; + } + + replaywin = (unsigned)replaywin_int; + sptps_replaywin = replaywin; + } + +#ifndef DISABLE_LEGACY + /* Generate packet encryption key */ + + char *cipher; + + if(!get_config_string(lookup_config(config_tree, "Cipher"), &cipher)) { + cipher = xstrdup("aes-256-cbc"); + } + + if(!strcasecmp(cipher, "none")) { + myself->incipher = NULL; + } else if(!(myself->incipher = cipher_open_by_name(cipher))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unrecognized cipher type!"); + free(cipher); + return false; + } + + free(cipher); + + timeout_add(&keyexpire_timeout, keyexpire_handler, &keyexpire_timeout, &(struct timeval) { + keylifetime, rand() % 100000 + }); /* Check if we want to use message authentication codes... */ - if(get_config_string(lookup_config(config_tree, "Digest"), &digest)) { - if(!strcasecmp(digest, "none")) { - myself->indigest = NULL; - } else { - myself->indigest = EVP_get_digestbyname(digest); + int maclength = 4; + get_config_int(lookup_config(config_tree, "MACLength"), &maclength); - if(!myself->indigest) { - logger(LOG_ERR, "Unrecognized digest type!"); - free(digest); - return false; - } - } + if(maclength < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus MAC length!"); + return false; + } + char *digest; + + if(!get_config_string(lookup_config(config_tree, "Digest"), &digest)) { + digest = xstrdup("sha256"); + } + + if(!strcasecmp(digest, "none")) { + myself->indigest = NULL; + } else if(!(myself->indigest = digest_open_by_name(digest, maclength))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unrecognized digest type!"); free(digest); - } else { - myself->indigest = EVP_sha256(); + return false; } - myself->connection->outdigest = EVP_sha256(); - - if(get_config_int(lookup_config(config_tree, "MACLength"), &myself->inmaclength)) { - if(myself->indigest) { - if(myself->inmaclength > EVP_MD_size(myself->indigest)) { - logger(LOG_ERR, "MAC length exceeds size of digest!"); - return false; - } else if(myself->inmaclength < 0) { - logger(LOG_ERR, "Bogus MAC length!"); - return false; - } - } - } else { - myself->inmaclength = 4; - } - - myself->connection->outmaclength = 0; + free(digest); +#endif /* Compression */ if(get_config_int(lookup_config(config_tree, "Compression"), &myself->incompression)) { if(myself->incompression < 0 || myself->incompression > 11) { - logger(LOG_ERR, "Bogus compression level!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus compression level!"); return false; } } else { @@ -812,13 +1034,13 @@ static bool setup_myself(void) { myself->nexthop = myself; myself->via = myself; myself->status.reachable = true; + myself->last_state_change = now.tv_sec; + myself->status.sptps = experimental; node_add(myself); graph(); - if(strictsubnets) { - load_all_subnets(); - } + load_all_nodes(); /* Open device */ @@ -833,6 +1055,12 @@ static bool setup_myself(void) { devops = multicast_devops; } +#ifdef HAVE_SYS_UN_H + else if(!strcasecmp(type, "fd")) { + devops = fd_devops; + } + +#endif #ifdef ENABLE_UML else if(!strcasecmp(type, "uml")) { devops = uml_devops; @@ -848,32 +1076,16 @@ static bool setup_myself(void) { free(type); } + get_config_bool(lookup_config(config_tree, "DeviceStandby"), &device_standby); + if(!devops.setup()) { return false; } - /* Run tinc-up script to further initialize the tap interface */ - xasprintf(&envp[0], "NETNAME=%s", netname ? netname : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? device : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? iface : ""); - xasprintf(&envp[3], "NAME=%s", myself->name); - -#ifdef HAVE_MINGW - Sleep(1000); -#endif -#ifdef HAVE_CYGWIN - sleep(1); -#endif - execute_script("tinc-up", envp); - - for(i = 0; i < 4; i++) { - free(envp[i]); + if(device_fd >= 0) { + io_add(&device_io, handle_device_data, NULL, device_fd, IO_READ); } - /* Run subnet-up scripts for our own subnets */ - - subnet_update(myself, NULL, true); - /* Open sockets */ if(!do_detach && getenv("LISTEN_FDS")) { @@ -886,33 +1098,34 @@ static bool setup_myself(void) { #endif if(listen_sockets > MAXSOCKETS) { - logger(LOG_ERR, "Too many listening sockets"); + logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets"); return false; } - for(i = 0; i < listen_sockets; i++) { + for(int i = 0; i < listen_sockets; i++) { salen = sizeof(sa); if(getsockname(i + 3, &sa.sa, &salen) < 0) { - logger(LOG_ERR, "Could not get address of listen fd %d: %s", i + 3, sockstrerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get address of listen fd %d: %s", i + 3, sockstrerror(sockerrno)); return false; } - listen_socket[i].tcp = i + 3; - #ifdef FD_CLOEXEC fcntl(i + 3, F_SETFD, FD_CLOEXEC); #endif - listen_socket[i].udp = setup_vpn_in_socket(&sa); + int udp_fd = setup_vpn_in_socket(&sa); - if(listen_socket[i].udp < 0) { + if(udp_fd < 0) { return false; } - ifdebug(CONNECTIONS) { + io_add(&listen_socket[i].tcp, (io_cb_t)handle_new_meta_connection, &listen_socket[i], i + 3, IO_READ); + io_add(&listen_socket[i].udp, (io_cb_t)handle_incoming_vpn_data, &listen_socket[i], udp_fd, IO_READ); + + if(debug_level >= DEBUG_CONNECTIONS) { hostname = sockaddr2hostname(&sa); - logger(LOG_NOTICE, "Listening on %s", hostname); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname); free(hostname); } @@ -920,94 +1133,44 @@ static bool setup_myself(void) { } } else { listen_sockets = 0; - cfg = lookup_config(config_tree, "BindToAddress"); + int cfgs = 0; - do { + for(config_t *cfg = lookup_config(config_tree, "BindToAddress"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + cfgs++; get_config_string(cfg, &address); - if(cfg) { - cfg = lookup_config_next(config_tree, cfg); - } - - char *port = myport; - - if(address) { - char *space = strchr(address, ' '); - - if(space) { - *space++ = 0; - port = space; - } - - if(!strcmp(address, "*")) { - *address = 0; - } - } - - hint.ai_family = addressfamily; - hint.ai_socktype = SOCK_STREAM; - hint.ai_protocol = IPPROTO_TCP; - hint.ai_flags = AI_PASSIVE; - -#if HAVE_DECL_RES_INIT - // ensure glibc reloads /etc/resolv.conf. - res_init(); -#endif - err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai); - free(address); - - if(err || !ai) { - logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo", - gai_strerror(err)); + if(!add_listen_address(address, true)) { return false; } + } - for(aip = ai; aip; aip = aip->ai_next) { - if(listen_sockets >= MAXSOCKETS) { - logger(LOG_ERR, "Too many listening sockets"); - return false; - } + for(config_t *cfg = lookup_config(config_tree, "ListenAddress"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + cfgs++; + get_config_string(cfg, &address); - listen_socket[listen_sockets].tcp = - setup_listen_socket((sockaddr_t *) aip->ai_addr); - - if(listen_socket[listen_sockets].tcp < 0) { - continue; - } - - listen_socket[listen_sockets].udp = - setup_vpn_in_socket((sockaddr_t *) aip->ai_addr); - - if(listen_socket[listen_sockets].udp < 0) { - continue; - } - - ifdebug(CONNECTIONS) { - hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); - logger(LOG_NOTICE, "Listening on %s", hostname); - free(hostname); - } - - memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); - listen_sockets++; + if(!add_listen_address(address, false)) { + return false; } + } - freeaddrinfo(ai); - } while(cfg); + if(!cfgs) + if(!add_listen_address(address, NULL)) { + return false; + } } if(!listen_sockets) { - logger(LOG_ERR, "Unable to create any listening socket!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create any listening socket!"); return false; } /* If no Port option was specified, set myport to the port used by the first listening socket. */ - if(!port_specified) { + if(!port_specified || atoi(myport) == 0) { sockaddr_t sa; socklen_t salen = sizeof(sa); - if(!getsockname(listen_socket[0].udp, &sa.sa, &salen)) { + if(!getsockname(listen_socket[0].udp.fd, &sa.sa, &salen)) { free(myport); sockaddr2str(&sa, NULL, &myport); @@ -1017,9 +1180,36 @@ static bool setup_myself(void) { } } + xasprintf(&myself->hostname, "MYSELF port %s", myport); + myself->connection->hostname = xstrdup(myself->hostname); + + char *upnp = NULL; + get_config_string(lookup_config(config_tree, "UPnP"), &upnp); + bool upnp_tcp = false; + bool upnp_udp = false; + + if(upnp) { + if(!strcasecmp(upnp, "yes")) { + upnp_tcp = upnp_udp = true; + } else if(!strcasecmp(upnp, "udponly")) { + upnp_udp = true; + } + + free(upnp); + } + + if(upnp_tcp || upnp_udp) { +#ifdef HAVE_MINIUPNPC + upnp_init(upnp_tcp, upnp_udp); +#else + logger(DEBUG_ALWAYS, LOG_WARNING, "UPnP was requested, but tinc isn't built with miniupnpc support!"); +#endif + } + /* Done. */ - logger(LOG_NOTICE, "Ready"); + last_config_check = now.tv_sec; + return true; } @@ -1027,9 +1217,6 @@ static bool setup_myself(void) { initialize network */ bool setup_network(void) { - now = time(NULL); - - init_events(); init_connections(); init_subnets(); init_nodes(); @@ -1060,6 +1247,18 @@ bool setup_network(void) { return false; } + if(!init_control()) { + return false; + } + + if(!device_standby) { + device_enable(); + } + + /* Run subnet-up scripts for our own subnets */ + + subnet_update(myself, NULL, true); + return true; } @@ -1067,62 +1266,59 @@ bool setup_network(void) { close all open network connections */ void close_network_connections(void) { - avl_node_t *node, *next; - connection_t *c; - char *envp[5] = {0}; - int i; - - for(node = connection_tree->head; node; node = next) { + for(list_node_t *node = connection_list->head, *next; node; node = next) { next = node->next; - c = node->data; + connection_t *c = node->data; + + /* Keep control connections open until the end, so they know when we really terminated */ + if(c->status.control) { + c->socket = -1; + } + c->outgoing = NULL; terminate_connection(c, false); } - for(list_node_t *node = outgoing_list->head; node; node = node->next) { - outgoing_t *outgoing = node->data; - - if(outgoing->event) { - event_del(outgoing->event); - } + if(outgoing_list) { + list_delete_list(outgoing_list); } - list_delete_list(outgoing_list); - if(myself && myself->connection) { subnet_update(myself, NULL, false); - terminate_connection(myself->connection, false); - free_connection(myself->connection); + connection_del(myself->connection); } - for(i = 0; i < listen_sockets; i++) { - close(listen_socket[i].tcp); - close(listen_socket[i].udp); + for(int i = 0; i < listen_sockets; i++) { + io_del(&listen_socket[i].tcp); + io_del(&listen_socket[i].udp); + close(listen_socket[i].tcp.fd); + close(listen_socket[i].udp.fd); } - xasprintf(&envp[0], "NETNAME=%s", netname ? netname : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? device : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? iface : ""); - xasprintf(&envp[3], "NAME=%s", myself->name); - exit_requests(); exit_edges(); exit_subnets(); exit_nodes(); exit_connections(); - exit_events(); - execute_script("tinc-down", envp); - - if(myport) { - free(myport); + if(!device_standby) { + device_disable(); } - for(i = 0; i < 4; i++) { - free(envp[i]); + free(myport); + + if(device_fd >= 0) { + io_del(&device_io); } - devops.close(); + if(devops.close) { + devops.close(); + } + + exit_control(); + + free(scriptextension); + free(scriptinterpreter); return; } diff --git a/src/net_socket.c b/src/net_socket.c index 6195c16..206321c 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -1,7 +1,7 @@ /* net_socket.c -- Handle various kinds of sockets. Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2017 Guus Sliepen + 2000-2018 Guus Sliepen 2006 Scott Lamb 2009 Florian Forster @@ -22,33 +22,33 @@ #include "system.h" -#include "avl_tree.h" +#include "address_cache.h" #include "conf.h" #include "connection.h" -#include "event.h" +#include "control_common.h" +#include "list.h" #include "logger.h" #include "meta.h" +#include "names.h" #include "net.h" #include "netutl.h" #include "protocol.h" -#include "proxy.h" #include "utils.h" #include "xalloc.h" -/* Needed on Mac OS/X */ -#ifndef SOL_TCP -#define SOL_TCP IPPROTO_TCP -#endif - int addressfamily = AF_UNSPEC; -int mintimeout = 0; int maxtimeout = 900; int seconds_till_retry = 5; -int udp_rcvbuf = 0; -int udp_sndbuf = 0; +int udp_rcvbuf = 1024 * 1024; +int udp_sndbuf = 1024 * 1024; +int max_connection_burst = 10; +int fwmark; listen_socket_t listen_socket[MAXSOCKETS]; int listen_sockets; +#ifndef HAVE_MINGW +io_t unix_socket; +#endif list_t *outgoing_list = NULL; /* Setup sockets */ @@ -60,21 +60,21 @@ static void configure_tcp(connection_t *c) { int flags = fcntl(c->socket, F_GETFL); if(fcntl(c->socket, F_SETFL, flags | O_NONBLOCK) < 0) { - logger(LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "fcntl for %s fd %d: %s", c->hostname, c->socket, strerror(errno)); } #elif defined(WIN32) unsigned long arg = 1; if(ioctlsocket(c->socket, FIONBIO, &arg) != 0) { - logger(LOG_ERR, "ioctlsocket for %s: %s", c->hostname, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "ioctlsocket for %s fd %d: %s", c->hostname, c->socket, sockstrerror(sockerrno)); } #endif -#if defined(SOL_TCP) && defined(TCP_NODELAY) +#if defined(TCP_NODELAY) option = 1; - setsockopt(c->socket, SOL_TCP, TCP_NODELAY, (void *)&option, sizeof(option)); + setsockopt(c->socket, IPPROTO_TCP, TCP_NODELAY, (void *)&option, sizeof(option)); #endif #if defined(IP_TOS) && defined(IPTOS_LOWDELAY) @@ -86,6 +86,14 @@ static void configure_tcp(connection_t *c) { option = IPTOS_LOWDELAY; setsockopt(c->socket, IPPROTO_IPV6, IPV6_TCLASS, (void *)&option, sizeof(option)); #endif + +#if defined(SO_MARK) + + if(fwmark) { + setsockopt(c->socket, SOL_SOCKET, SO_MARK, (void *)&fwmark, sizeof(fwmark)); + } + +#endif } static bool bind_to_interface(int sd) { @@ -104,22 +112,58 @@ static bool bind_to_interface(int sd) { memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; - free(iface); status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)); if(status) { - logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface, + sockstrerror(sockerrno)); return false; } #else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */ - logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface"); + (void)sd; + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface"); #endif return true; } +static bool bind_to_address(connection_t *c) { + int s = -1; + + for(int i = 0; i < listen_sockets && listen_socket[i].bindto; i++) { + if(listen_socket[i].sa.sa.sa_family != c->address.sa.sa_family) { + continue; + } + + if(s >= 0) { + return false; + } + + s = i; + } + + if(s < 0) { + return false; + } + + sockaddr_t sa = listen_socket[s].sa; + + if(sa.sa.sa_family == AF_INET) { + sa.in.sin_port = 0; + } else if(sa.sa.sa_family == AF_INET6) { + sa.in6.sin6_port = 0; + } + + if(bind(c->socket, &sa.sa, SALEN(sa.sa))) { + logger(DEBUG_CONNECTIONS, LOG_WARNING, "Can't bind outgoing socket: %s", sockstrerror(sockerrno)); + return false; + } + + return true; +} + int setup_listen_socket(const sockaddr_t *sa) { int nfd; char *addrstr; @@ -129,7 +173,7 @@ int setup_listen_socket(const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_STREAM, IPPROTO_TCP); if(nfd < 0) { - ifdebug(STATUS) logger(LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_STATUS, LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); return -1; } @@ -152,37 +196,46 @@ int setup_listen_socket(const sockaddr_t *sa) { #warning IPV6_V6ONLY not defined #endif - if(get_config_string(lookup_config(config_tree, "BindToInterface"), &iface)) { +#if defined(SO_MARK) + + if(fwmark) { + setsockopt(nfd, SOL_SOCKET, SO_MARK, (void *)&fwmark, sizeof(fwmark)); + } + +#endif + + if(get_config_string + (lookup_config(config_tree, "BindToInterface"), &iface)) { #if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; - free(iface); if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr))) { closesocket(nfd); - logger(LOG_ERR, "Can't bind to interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface, + sockstrerror(sockerrno)); return -1; } #else - logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface"); #endif } if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } if(listen(nfd, 3)) { closesocket(nfd); - logger(LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); return -1; } @@ -197,7 +250,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); if(nfd < 0) { - logger(LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); return -1; } @@ -211,7 +264,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) { closesocket(nfd); - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); return -1; } @@ -222,7 +275,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { closesocket(nfd); - logger(LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); return -1; } } @@ -233,11 +286,11 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof(option)); if(udp_rcvbuf && setsockopt(nfd, SOL_SOCKET, SO_RCVBUF, (void *)&udp_rcvbuf, sizeof(udp_rcvbuf))) { - logger(LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, sockstrerror(sockerrno)); } if(udp_sndbuf && setsockopt(nfd, SOL_SOCKET, SO_SNDBUF, (void *)&udp_sndbuf, sizeof(udp_sndbuf))) { - logger(LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, sockstrerror(sockerrno)); } #if defined(IPV6_V6ONLY) @@ -282,6 +335,14 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { setsockopt(nfd, IPPROTO_IPV6, IPV6_DONTFRAG, (void *)&option, sizeof(option)); } +#endif + +#if defined(SO_MARK) + + if(fwmark) { + setsockopt(nfd, SOL_SOCKET, SO_MARK, (void *)&fwmark, sizeof(fwmark)); + } + #endif if(!bind_to_interface(nfd)) { @@ -292,7 +353,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } @@ -300,53 +361,46 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { return nfd; } /* int setup_vpn_in_socket */ +static void retry_outgoing_handler(void *data) { + setup_outgoing_connection(data, true); +} + void retry_outgoing(outgoing_t *outgoing) { outgoing->timeout += 5; - if(outgoing->timeout < mintimeout) { - outgoing->timeout = mintimeout; - } - if(outgoing->timeout > maxtimeout) { outgoing->timeout = maxtimeout; } - if(outgoing->event) { - event_del(outgoing->event); - } + timeout_add(&outgoing->ev, retry_outgoing_handler, outgoing, &(struct timeval) { + outgoing->timeout, rand() % 100000 + }); - outgoing->event = new_event(); - outgoing->event->handler = (event_handler_t) setup_outgoing_connection; - outgoing->event->time = now + outgoing->timeout; - outgoing->event->data = outgoing; - event_add(outgoing->event); - - ifdebug(CONNECTIONS) logger(LOG_NOTICE, - "Trying to re-establish outgoing connection in %d seconds", - outgoing->timeout); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Trying to re-establish outgoing connection in %d seconds", outgoing->timeout); } void finish_connecting(connection_t *c) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); - c->last_ping_time = now; + c->last_ping_time = now.tv_sec; + c->status.connecting = false; send_id(c); } -static void do_outgoing_pipe(connection_t *c, char *command) { +static void do_outgoing_pipe(connection_t *c, const char *command) { #ifndef HAVE_MINGW int fd[2]; if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { - logger(LOG_ERR, "Could not create socketpair: %s\n", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s", sockstrerror(sockerrno)); return; } if(fork()) { c->socket = fd[0]; close(fd[1]); - ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Using proxy %s", command); + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Using proxy %s", command); return; } @@ -375,137 +429,143 @@ static void do_outgoing_pipe(connection_t *c, char *command) { int result = system(command); if(result < 0) { - logger(LOG_ERR, "Could not execute %s: %s\n", command, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not execute %s: %s", command, strerror(errno)); } else if(result) { - logger(LOG_ERR, "%s exited with non-zero status %d", command, result); + logger(DEBUG_ALWAYS, LOG_ERR, "%s exited with non-zero status %d", command, result); } exit(result); #else - logger(LOG_ERR, "Proxy type exec not supported on this platform!"); + (void)c; + (void)command; + logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type exec not supported on this platform!"); return; #endif } -static bool is_valid_host_port(const char *host, const char *port) { - for(const char *p = host; *p; p++) - if(!isalnum(*p) && *p != '-' && *p != '.') { - return false; - } - - for(const char *p = port; *p; p++) - if(!isalnum(*p)) { - return false; - } - - return true; -} - -void do_outgoing_connection(connection_t *c) { - struct addrinfo *proxyai = NULL; - int result; - - if(!c->outgoing) { - logger(LOG_ERR, "do_outgoing_connection() for %s called without c->outgoing", c->name); - abort(); +static void handle_meta_write(connection_t *c) { + if(c->outbuf.len <= c->outbuf.offset) { + return; } -begin: + ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0); + + if(outlen <= 0) { + if(!sockerrno || sockerrno == EPIPE) { + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", c->name, c->hostname); + } else if(sockwouldblock(sockerrno)) { + logger(DEBUG_META, LOG_DEBUG, "Sending %d bytes to %s (%s) would block", c->outbuf.len - c->outbuf.offset, c->name, c->hostname); + return; + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not send %d bytes of data to %s (%s): %s", c->outbuf.len - c->outbuf.offset, c->name, c->hostname, sockstrerror(sockerrno)); + } + + terminate_connection(c, c->edge); + return; + } + + buffer_read(&c->outbuf, outlen); + + if(!c->outbuf.len) { + io_set(&c->io, IO_READ); + } +} + +static void handle_meta_io(void *data, int flags) { + connection_t *c = data; + + if(c->status.connecting) { + /* + The event loop does not protect against spurious events. Verify that we are actually connected + by issuing an empty send() call. + + Note that the behavior of send() on potentially unconnected sockets differ between platforms: + +------------+-----------+-------------+-----------+ + | Event | POSIX | Linux | Windows | + +------------+-----------+-------------+-----------+ + | Spurious | ENOTCONN | EWOULDBLOCK | ENOTCONN | + | Failed | ENOTCONN | (cause) | ENOTCONN | + | Successful | (success) | (success) | (success) | + +------------+-----------+-------------+-----------+ + */ + if(send(c->socket, NULL, 0, 0) != 0) { + if(sockwouldblock(sockerrno)) { + return; + } + + int socket_error; + + if(!socknotconn(sockerrno)) { + socket_error = sockerrno; + } else { + socklen_t len = sizeof(socket_error); + getsockopt(c->socket, SOL_SOCKET, SO_ERROR, (void *)&socket_error, &len); + } + + if(socket_error) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Error while connecting to %s (%s): %s", c->name, c->hostname, sockstrerror(socket_error)); + terminate_connection(c, false); + } - if(!c->outgoing->ai) { - if(!c->outgoing->cfg) { - ifdebug(CONNECTIONS) logger(LOG_ERR, "Could not set up a meta connection to %s", - c->name); - c->status.remove = true; - retry_outgoing(c->outgoing); - c->outgoing = NULL; return; } - char *address, *port, *space; - - get_config_string(c->outgoing->cfg, &address); - - space = strchr(address, ' '); - - if(space) { - port = xstrdup(space + 1); - *space = 0; - } else { - if(!get_config_string(lookup_config(c->config_tree, "Port"), &port)) { - port = xstrdup("655"); - } - } - - c->outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); - - // If we cannot resolve the address, maybe we are using a proxy that can? - if(!c->outgoing->ai && proxytype != PROXY_NONE && is_valid_host_port(address, port)) { - memset(&c->address, 0, sizeof(c->address)); - c->address.sa.sa_family = AF_UNKNOWN; - c->address.unknown.address = address; - c->address.unknown.port = port; - } else { - free(address); - free(port); - } - - c->outgoing->aip = c->outgoing->ai; - c->outgoing->cfg = lookup_config_next(c->config_tree, c->outgoing->cfg); - - if(!c->outgoing->ai && proxytype != PROXY_NONE) { - goto connect; - } + c->status.connecting = false; + finish_connecting(c); } - if(!c->outgoing->aip) { - if(c->outgoing->ai) { - freeaddrinfo(c->outgoing->ai); - } + if(flags & IO_WRITE) { + handle_meta_write(c); + } else { + handle_meta_connection_data(c); + } +} - c->outgoing->ai = NULL; - goto begin; - } - - memcpy(&c->address, c->outgoing->aip->ai_addr, c->outgoing->aip->ai_addrlen); - c->outgoing->aip = c->outgoing->aip->ai_next; - -connect: - - if(c->hostname) { - free(c->hostname); +bool do_outgoing_connection(outgoing_t *outgoing) { + const sockaddr_t *sa; + struct addrinfo *proxyai = NULL; + int result; + +begin: + sa = get_recent_address(outgoing->node->address_cache); + + if(!sa) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->node->name); + retry_outgoing(outgoing); + return false; } + connection_t *c = new_connection(); + c->outgoing = outgoing; + memcpy(&c->address, sa, SALEN(sa->sa)); c->hostname = sockaddr2hostname(&c->address); - ifdebug(CONNECTIONS) logger(LOG_INFO, "Trying to connect to %s (%s)", c->name, - c->hostname); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", outgoing->node->name, c->hostname); if(!proxytype) { c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); + configure_tcp(c); } else if(proxytype == PROXY_EXEC) { - c->status.proxy_passed = true; do_outgoing_pipe(c, proxyhost); } else { proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM); if(!proxyai) { + free_connection(c); goto begin; } - ifdebug(CONNECTIONS) logger(LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport); c->socket = socket(proxyai->ai_family, SOCK_STREAM, IPPROTO_TCP); + configure_tcp(c); } if(c->socket == -1) { - ifdebug(CONNECTIONS) logger(LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); + logger(DEBUG_CONNECTIONS, LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); + free_connection(c); goto begin; } - if(proxytype != PROXY_EXEC) { - configure_tcp(c); - } - #ifdef FD_CLOEXEC fcntl(c->socket, F_SETFD, FD_CLOEXEC); #endif @@ -521,35 +581,7 @@ connect: #endif bind_to_interface(c->socket); - - int b = -1; - - for(int i = 0; i < listen_sockets; i++) { - if(listen_socket[i].sa.sa.sa_family == c->address.sa.sa_family) { - if(b == -1) { - b = i; - } else { - b = -1; - break; - } - } - } - - if(b != -1) { - sockaddr_t sa = listen_socket[b].sa; - - if(sa.sa.sa_family == AF_INET) { - sa.in.sin_port = 0; - } else if(sa.sa.sa_family == AF_INET6) { - sa.in6.sin6_port = 0; - } - - if(bind(c->socket, &sa.sa, SALEN(sa.sa))) { - char *addrstr = sockaddr2hostname(&sa); - logger(LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); - free(addrstr); - } - } + bind_to_address(c); } /* Connect */ @@ -559,175 +591,275 @@ connect: } else if(proxytype == PROXY_EXEC) { result = 0; } else { + if(!proxyai) { + abort(); + } + result = connect(c->socket, proxyai->ai_addr, proxyai->ai_addrlen); freeaddrinfo(proxyai); } - now = time(NULL); - - if(result == -1) { - if(sockinprogress(sockerrno)) { - c->last_ping_time = now; - c->status.connecting = true; - return; - } - - closesocket(c->socket); - - ifdebug(CONNECTIONS) logger(LOG_ERR, "%s: %s", c->hostname, sockstrerror(sockerrno)); + if(result == -1 && !sockinprogress(sockerrno)) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not connect to %s (%s): %s", outgoing->node->name, c->hostname, sockstrerror(sockerrno)); + free_connection(c); goto begin; } - finish_connecting(c); + /* Now that there is a working socket, fill in the rest and register this connection. */ - return; -} - -void setup_outgoing_connection(outgoing_t *outgoing) { - connection_t *c; - node_t *n; - - outgoing->event = NULL; - - n = lookup_node(outgoing->name); - - if(n) - if(n->connection) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "Already connected to %s", outgoing->name); - - n->connection->outgoing = outgoing; - return; - } - - c = new_connection(); - c->name = xstrdup(outgoing->name); + c->last_ping_time = time(NULL); + c->status.connecting = true; + c->name = xstrdup(outgoing->node->name); +#ifndef DISABLE_LEGACY c->outcipher = myself->connection->outcipher; c->outdigest = myself->connection->outdigest; +#endif c->outmaclength = myself->connection->outmaclength; c->outcompression = myself->connection->outcompression; - - init_configuration(&c->config_tree); - - if(!read_connection_config(c)) { - free_connection(c); - outgoing->timeout = maxtimeout; - retry_outgoing(outgoing); - return; - } - - outgoing->cfg = lookup_config(c->config_tree, "Address"); - - if(!outgoing->cfg) { - logger(LOG_ERR, "No address specified for %s", c->name); - free_connection(c); - outgoing->timeout = maxtimeout; - retry_outgoing(outgoing); - return; - } - - c->outgoing = outgoing; - c->last_ping_time = now; + c->last_ping_time = now.tv_sec; connection_add(c); - do_outgoing_connection(c); + io_add(&c->io, handle_meta_io, c, c->socket, IO_READ | IO_WRITE); + + return true; +} + +void setup_outgoing_connection(outgoing_t *outgoing, bool verbose) { + (void)verbose; + timeout_del(&outgoing->ev); + + node_t *n = outgoing->node; + + if(!n->address_cache) { + n->address_cache = open_address_cache(n); + } + + if(n->connection) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", n->name); + + if(!n->connection->outgoing) { + n->connection->outgoing = outgoing; + return; + } else { + goto remove; + } + } + + do_outgoing_connection(outgoing); + return; + +remove: + list_delete(outgoing_list, outgoing); } /* accept a new tcp connect and create a new connection */ -bool handle_new_meta_connection(int sock) { - static const int max_accept_burst = 10; - static int last_accept_burst; - static int last_accept_time; +void handle_new_meta_connection(void *data, int flags) { + (void)flags; + listen_socket_t *l = data; connection_t *c; sockaddr_t sa; int fd; socklen_t len = sizeof(sa); - fd = accept(sock, &sa.sa, &len); + fd = accept(l->tcp.fd, &sa.sa, &len); if(fd < 0) { - logger(LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); - return false; - } - - if(last_accept_time == now) { - last_accept_burst++; - - if(last_accept_burst >= max_accept_burst) { - if(last_accept_burst == max_accept_burst) { - ifdebug(CONNECTIONS) logger(LOG_WARNING, "Throttling incoming connections"); - } - - tarpit(fd); - return false; - } - } else { - last_accept_burst = 0; - last_accept_time = now; + logger(DEBUG_ALWAYS, LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); + return; } sockaddrunmap(&sa); + // Check if we get many connections from the same host + + static sockaddr_t prev_sa; + + if(!sockaddrcmp_noport(&sa, &prev_sa)) { + static int samehost_burst; + static int samehost_burst_time; + + if(now.tv_sec - samehost_burst_time > samehost_burst) { + samehost_burst = 0; + } else { + samehost_burst -= now.tv_sec - samehost_burst_time; + } + + samehost_burst_time = now.tv_sec; + samehost_burst++; + + if(samehost_burst > max_connection_burst) { + tarpit(fd); + return; + } + } + + memcpy(&prev_sa, &sa, sizeof(sa)); + + // Check if we get many connections from different hosts + + static int connection_burst; + static int connection_burst_time; + + if(now.tv_sec - connection_burst_time > connection_burst) { + connection_burst = 0; + } else { + connection_burst -= now.tv_sec - connection_burst_time; + } + + connection_burst_time = now.tv_sec; + connection_burst++; + + if(connection_burst >= max_connection_burst) { + connection_burst = max_connection_burst; + tarpit(fd); + return; + } + + // Accept the new connection + c = new_connection(); c->name = xstrdup(""); +#ifndef DISABLE_LEGACY c->outcipher = myself->connection->outcipher; c->outdigest = myself->connection->outdigest; +#endif c->outmaclength = myself->connection->outmaclength; c->outcompression = myself->connection->outcompression; c->address = sa; c->hostname = sockaddr2hostname(&sa); c->socket = fd; - c->last_ping_time = now; + c->last_ping_time = now.tv_sec; - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection from %s", c->hostname); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection from %s", c->hostname); + + io_add(&c->io, handle_meta_io, c, c->socket, IO_READ); configure_tcp(c); connection_add(c); c->allow_request = ID; - - return true; } +#ifndef HAVE_MINGW +/* + accept a new UNIX socket connection +*/ +void handle_new_unix_connection(void *data, int flags) { + (void)flags; + io_t *io = data; + connection_t *c; + sockaddr_t sa; + int fd; + socklen_t len = sizeof(sa); + + fd = accept(io->fd, &sa.sa, &len); + + if(fd < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); + return; + } + + sockaddrunmap(&sa); + + c = new_connection(); + c->name = xstrdup(""); + c->address = sa; + c->hostname = xstrdup("localhost port unix"); + c->socket = fd; + c->last_ping_time = now.tv_sec; + + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection from %s", c->hostname); + + io_add(&c->io, handle_meta_io, c, c->socket, IO_READ); + + connection_add(c); + + c->allow_request = ID; +} +#endif + static void free_outgoing(outgoing_t *outgoing) { - if(outgoing->ai) { - freeaddrinfo(outgoing->ai); - } - - if(outgoing->name) { - free(outgoing->name); - } - + timeout_del(&outgoing->ev); free(outgoing); } void try_outgoing_connections(void) { - static config_t *cfg = NULL; - char *name; - outgoing_t *outgoing; + /* If there is no outgoing list yet, create one. Otherwise, mark all outgoings as deleted. */ - outgoing_list = list_alloc((list_action_t)free_outgoing); + if(!outgoing_list) { + outgoing_list = list_alloc((list_action_t)free_outgoing); + } else { + for list_each(outgoing_t, outgoing, outgoing_list) { + outgoing->timeout = -1; + } + } - for(cfg = lookup_config(config_tree, "ConnectTo"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + /* Make sure there is one outgoing_t in the list for each ConnectTo. */ + + for(config_t *cfg = lookup_config(config_tree, "ConnectTo"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + char *name; get_config_string(cfg, &name); if(!check_id(name)) { - logger(LOG_ERR, + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for outgoing connection in %s line %d", cfg->file, cfg->line); free(name); continue; } - outgoing = xmalloc_and_zero(sizeof(*outgoing)); - outgoing->name = name; - list_insert_tail(outgoing_list, outgoing); - setup_outgoing_connection(outgoing); + if(!strcmp(name, myself->name)) { + free(name); + continue; + } + + bool found = false; + + for list_each(outgoing_t, outgoing, outgoing_list) { + if(!strcmp(outgoing->node->name, name)) { + found = true; + outgoing->timeout = 0; + break; + } + } + + if(!found) { + outgoing_t *outgoing = xzalloc(sizeof(*outgoing)); + node_t *n = lookup_node(name); + + if(!n) { + n = new_node(); + n->name = xstrdup(name); + node_add(n); + } + + outgoing->node = n; + list_insert_tail(outgoing_list, outgoing); + setup_outgoing_connection(outgoing, true); + } } + + /* Terminate any connections whose outgoing_t is to be deleted. */ + + for list_each(connection_t, c, connection_list) { + if(c->outgoing && c->outgoing->timeout == -1) { + c->outgoing = NULL; + logger(DEBUG_CONNECTIONS, LOG_INFO, "No more outgoing connection to %s", c->name); + terminate_connection(c, c->edge); + } + } + + /* Delete outgoing_ts for which there is no ConnectTo. */ + + for list_each(outgoing_t, outgoing, outgoing_list) + if(outgoing->timeout == -1) { + list_delete_node(outgoing_list, node); + } } diff --git a/src/netutl.c b/src/netutl.c index abe3d87..2916e9a 100644 --- a/src/netutl.c +++ b/src/netutl.c @@ -1,7 +1,7 @@ /* netutl.c -- some supporting network utility code Copyright (C) 1998-2005 Ivo Timmermans - 2000-2016 Guus Sliepen + 2000-2013 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 @@ -33,21 +33,19 @@ bool hostnames = false; Return NULL on failure. */ struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype) { - struct addrinfo *ai = NULL, hint = {0}; + struct addrinfo *ai, hint = {0}; int err; hint.ai_family = addressfamily; hint.ai_socktype = socktype; #if HAVE_DECL_RES_INIT - // ensure glibc reloads /etc/resolv.conf. res_init(); #endif err = getaddrinfo(address, service, &hint, &ai); if(err) { - logger(LOG_WARNING, "Error looking up %s port %s: %s", address, - service, gai_strerror(err)); + logger(DEBUG_ALWAYS, LOG_WARNING, "Error looking up %s port %s: %s", address, service, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); return NULL; } @@ -55,8 +53,8 @@ struct addrinfo *str2addrinfo(const char *address, const char *service, int sock } sockaddr_t str2sockaddr(const char *address, const char *port) { - struct addrinfo *ai = NULL, hint = {0}; - sockaddr_t result; + struct addrinfo *ai, hint = {0}; + sockaddr_t result = {0}; int err; hint.ai_family = AF_UNSPEC; @@ -66,8 +64,7 @@ sockaddr_t str2sockaddr(const char *address, const char *port) { err = getaddrinfo(address, port, &hint, &ai); if(err || !ai) { - ifdebug(SCARY_THINGS) - logger(LOG_DEBUG, "Unknown type address %s port %s", address, port); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Unknown type address %s port %s", address, port); result.sa.sa_family = AF_UNKNOWN; result.unknown.address = xstrdup(address); result.unknown.port = xstrdup(port); @@ -86,7 +83,17 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { char *scopeid; int err; - if(sa->sa.sa_family == AF_UNKNOWN) { + if(sa->sa.sa_family == AF_UNSPEC) { + if(addrstr) { + *addrstr = xstrdup("unspec"); + } + + if(portstr) { + *portstr = xstrdup("unspec"); + } + + return; + } else if(sa->sa.sa_family == AF_UNKNOWN) { if(addrstr) { *addrstr = xstrdup(sa->unknown.address); } @@ -101,8 +108,7 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof(address), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); if(err) { - logger(LOG_ERR, "Error while translating addresses: %s", - gai_strerror(err)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while translating addresses: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); abort(); } @@ -127,7 +133,10 @@ char *sockaddr2hostname(const sockaddr_t *sa) { char port[NI_MAXSERV] = "unknown"; int err; - if(sa->sa.sa_family == AF_UNKNOWN) { + if(sa->sa.sa_family == AF_UNSPEC) { + xasprintf(&str, "unspec port unspec"); + return str; + } else if(sa->sa.sa_family == AF_UNKNOWN) { xasprintf(&str, "%s port %s", sa->unknown.address, sa->unknown.port); return str; } @@ -136,8 +145,7 @@ char *sockaddr2hostname(const sockaddr_t *sa) { hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV)); if(err) { - logger(LOG_ERR, "Error while looking up hostname: %s", - gai_strerror(err)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while looking up hostname: %s", err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); } xasprintf(&str, "%s port %s", address, port); @@ -168,7 +176,7 @@ int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)); default: - logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } @@ -215,7 +223,7 @@ int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof(a->in6.sin6_port)); default: - logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } @@ -269,79 +277,3 @@ void sockaddr_setport(sockaddr_t *sa, const char *port) { return; } } - -/* Subnet mask handling */ - -int maskcmp(const void *va, const void *vb, int masklen) { - int i, m, result; - const char *a = va; - const char *b = vb; - - for(m = masklen, i = 0; m >= 8; m -= 8, i++) { - result = a[i] - b[i]; - - if(result) { - return result; - } - } - - if(m) - return (a[i] & (0x100 - (1 << (8 - m)))) - - (b[i] & (0x100 - (1 << (8 - m)))); - - return 0; -} - -void mask(void *va, int masklen, int len) { - int i; - char *a = va; - - i = masklen / 8; - masklen %= 8; - - if(masklen) { - a[i++] &= (0x100 - (1 << (8 - masklen))); - } - - for(; i < len; i++) { - a[i] = 0; - } -} - -void maskcpy(void *va, const void *vb, int masklen, int len) { - int i, m; - char *a = va; - const char *b = vb; - - for(m = masklen, i = 0; m >= 8; m -= 8, i++) { - a[i] = b[i]; - } - - if(m) { - a[i] = b[i] & (0x100 - (1 << (8 - m))); - i++; - } - - for(; i < len; i++) { - a[i] = 0; - } -} - -bool maskcheck(const void *va, int masklen, int len) { - int i; - const char *a = va; - - i = masklen / 8; - masklen %= 8; - - if(masklen && a[i++] & (0xff >> masklen)) { - return false; - } - - for(; i < len; i++) - if(a[i] != 0) { - return false; - } - - return true; -} diff --git a/src/netutl.h b/src/netutl.h index cc3ccab..4aa1542 100644 --- a/src/netutl.h +++ b/src/netutl.h @@ -4,7 +4,7 @@ /* netutl.h -- header file for netutl.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2016 Guus Sliepen + 2000-2013 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 @@ -25,19 +25,15 @@ extern bool hostnames; -extern struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype); +extern struct addrinfo *str2addrinfo(const char *address, const char *service, int socktype) __attribute__((__malloc__)); extern sockaddr_t str2sockaddr(const char *address, const char *port); -extern void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr); -extern char *sockaddr2hostname(const sockaddr_t *sa); +extern void sockaddr2str(const sockaddr_t *sa, char **address, char **port); +extern char *sockaddr2hostname(const sockaddr_t *sa) __attribute__((__malloc__)); extern int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b); extern int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b); extern void sockaddrunmap(sockaddr_t *sa); extern void sockaddrfree(sockaddr_t *sa); extern void sockaddrcpy(sockaddr_t *dest, const sockaddr_t *src); extern void sockaddr_setport(sockaddr_t *sa, const char *port); -extern int maskcmp(const void *a, const void *b, int masklen); -extern void maskcpy(void *dest, const void *src, int masklen, int len); -extern void mask(void *mask, int masklen, int len); -extern bool maskcheck(const void *mask, int masklen, int len); #endif diff --git a/src/node.c b/src/node.c index 03be21c..8f4b6ee 100644 --- a/src/node.c +++ b/src/node.c @@ -1,6 +1,6 @@ /* node.c -- node tree management - Copyright (C) 2001-2016 Guus Sliepen , + Copyright (C) 2001-2013 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -20,16 +20,22 @@ #include "system.h" -#include "avl_tree.h" +#include "address_cache.h" +#include "control_common.h" +#include "hash.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "node.h" +#include "splay_tree.h" #include "utils.h" #include "xalloc.h" -avl_tree_t *node_tree; /* Known nodes, sorted by name */ -avl_tree_t *node_udp_tree; /* Known nodes, sorted by address and port */ +#include "ed25519/sha512.h" + +splay_tree_t *node_tree; +static splay_tree_t *node_id_tree; +static splay_tree_t *node_udp_tree; node_t *myself; @@ -37,51 +43,49 @@ static int node_compare(const node_t *a, const node_t *b) { return strcmp(a->name, b->name); } +static int node_id_compare(const node_t *a, const node_t *b) { + return memcmp(&a->id, &b->id, sizeof(node_id_t)); +} + static int node_udp_compare(const node_t *a, const node_t *b) { - return sockaddrcmp(&a->address, &b->address); + int result = sockaddrcmp(&a->address, &b->address); + + if(result) { + return result; + } + + return (a->name && b->name) ? strcmp(a->name, b->name) : 0; } void init_nodes(void) { - node_tree = avl_alloc_tree((avl_compare_t) node_compare, (avl_action_t) free_node); - node_udp_tree = avl_alloc_tree((avl_compare_t) node_udp_compare, NULL); + node_tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node); + node_id_tree = splay_alloc_tree((splay_compare_t) node_id_compare, NULL); + node_udp_tree = splay_alloc_tree((splay_compare_t) node_udp_compare, NULL); } void exit_nodes(void) { - avl_delete_tree(node_udp_tree); - avl_delete_tree(node_tree); + splay_delete_tree(node_udp_tree); + splay_delete_tree(node_id_tree); + splay_delete_tree(node_tree); } node_t *new_node(void) { - node_t *n = xmalloc_and_zero(sizeof(*n)); + node_t *n = xzalloc(sizeof(*n)); if(replaywin) { - n->late = xmalloc_and_zero(replaywin); + n->late = xzalloc(replaywin); } n->subnet_tree = new_subnet_tree(); n->edge_tree = new_edge_tree(); - n->inctx = EVP_CIPHER_CTX_new(); - n->outctx = EVP_CIPHER_CTX_new(); - - if(!n->inctx || !n->outctx) { - abort(); - } - n->mtu = MTU; n->maxmtu = MTU; + n->udp_ping_rtt = -1; return n; } void free_node(node_t *n) { - if(n->inkey) { - free(n->inkey); - } - - if(n->outkey) { - free(n->outkey); - } - if(n->subnet_tree) { free_subnet_tree(n->subnet_tree); } @@ -92,51 +96,51 @@ void free_node(node_t *n) { sockaddrfree(&n->address); - EVP_CIPHER_CTX_free(n->outctx); - EVP_CIPHER_CTX_free(n->inctx); +#ifndef DISABLE_LEGACY + cipher_close(n->incipher); + digest_close(n->indigest); + cipher_close(n->outcipher); + digest_close(n->outdigest); +#endif - if(n->mtuevent) { - event_del(n->mtuevent); - } + ecdsa_free(n->ecdsa); + sptps_stop(&n->sptps); - if(n->hostname) { - free(n->hostname); - } + timeout_del(&n->udp_ping_timeout); - if(n->name) { - free(n->name); - } + free(n->hostname); + free(n->name); + free(n->late); - if(n->late) { - free(n->late); + if(n->address_cache) { + close_address_cache(n->address_cache); } free(n); } void node_add(node_t *n) { - avl_insert(node_tree, n); + unsigned char buf[64]; + sha512(n->name, strlen(n->name), buf); + memcpy(&n->id, buf, sizeof(n->id)); + + splay_insert(node_tree, n); + splay_insert(node_id_tree, n); } void node_del(node_t *n) { - avl_node_t *node, *next; - edge_t *e; - subnet_t *s; + splay_delete(node_udp_tree, n); - for(node = n->subnet_tree->head; node; node = next) { - next = node->next; - s = node->data; + for splay_each(subnet_t, s, n->subnet_tree) { subnet_del(n, s); } - for(node = n->edge_tree->head; node; node = next) { - next = node->next; - e = node->data; + for splay_each(edge_t, e, n->edge_tree) { edge_del(e); } - avl_delete(node_udp_tree, n); - avl_delete(node_tree, n); + splay_delete(node_id_tree, n); + splay_delete(node_tree, n); } node_t *lookup_node(char *name) { @@ -144,56 +148,82 @@ node_t *lookup_node(char *name) { n.name = name; - return avl_search(node_tree, &n); + return splay_search(node_tree, &n); +} + +node_t *lookup_node_id(const node_id_t *id) { + node_t n = {.id = *id}; + return splay_search(node_id_tree, &n); } node_t *lookup_node_udp(const sockaddr_t *sa) { - node_t n = {0}; - - n.address = *sa; - n.name = NULL; - - return avl_search(node_udp_tree, &n); + node_t tmp = {.address = *sa}; + return splay_search(node_udp_tree, &tmp); } void update_node_udp(node_t *n, const sockaddr_t *sa) { if(n == myself) { - logger(LOG_WARNING, "Trying to update UDP address of myself!"); + logger(DEBUG_ALWAYS, LOG_WARNING, "Trying to update UDP address of myself!"); return; } - avl_delete(node_udp_tree, n); - - if(n->hostname) { - free(n->hostname); - } + splay_delete(node_udp_tree, n); if(sa) { n->address = *sa; + n->sock = 0; + + for(int i = 0; i < listen_sockets; i++) { + if(listen_socket[i].sa.sa.sa_family == sa->sa.sa_family) { + n->sock = i; + break; + } + } + + splay_insert(node_udp_tree, n); + free(n->hostname); n->hostname = sockaddr2hostname(&n->address); - avl_insert(node_udp_tree, n); - ifdebug(PROTOCOL) logger(LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); - } else { - memset(&n->address, 0, sizeof(n->address)); - n->hostname = NULL; - ifdebug(PROTOCOL) logger(LOG_DEBUG, "UDP address of %s cleared", n->name); - } -} - -void dump_nodes(void) { - avl_node_t *node; - node_t *n; - - logger(LOG_DEBUG, "Nodes:"); - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - logger(LOG_DEBUG, " %s at %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s pmtu %d (min %d max %d)", - n->name, n->hostname, n->outcipher ? EVP_CIPHER_nid(n->outcipher) : 0, - n->outdigest ? EVP_MD_type(n->outdigest) : 0, n->outmaclength, n->outcompression, - n->options, bitfield_to_int(&n->status, sizeof(n->status)), n->nexthop ? n->nexthop->name : "-", - n->via ? n->via->name : "-", n->mtu, n->minmtu, n->maxmtu); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); } - logger(LOG_DEBUG, "End of nodes."); + /* invalidate UDP information - note that this is a security feature as well to make sure + we can't be tricked into flooding any random address with UDP packets */ + n->status.udp_confirmed = false; + n->maxrecentlen = 0; + n->mtuprobes = 0; + n->minmtu = 0; + n->maxmtu = MTU; +} + +bool dump_nodes(connection_t *c) { + for splay_each(node_t, n, node_tree) { + char id[2 * sizeof(n->id) + 1]; + + for(size_t c = 0; c < sizeof(n->id); ++c) { + snprintf(id + 2 * c, 3, "%02x", n->id.x[c]); + } + + id[sizeof(id) - 1] = 0; + send_request(c, "%d %d %s %s %s %d %d %d %d %x %x %s %s %d %d %d %d %ld %d %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, CONTROL, REQ_DUMP_NODES, + n->name, id, n->hostname ? n->hostname : "unknown port unknown", +#ifdef DISABLE_LEGACY + 0, 0, 0, +#else + cipher_get_nid(n->outcipher), digest_get_nid(n->outdigest), (int)digest_length(n->outdigest), +#endif + n->outcompression, n->options, bitfield_to_int(&n->status, sizeof(n->status)), + n->nexthop ? n->nexthop->name : "-", n->via && n->via->name ? n->via->name : "-", n->distance, + n->mtu, n->minmtu, n->maxmtu, (long)n->last_state_change, n->udp_ping_rtt, + n->in_packets, n->in_bytes, n->out_packets, n->out_bytes); + } + + return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES); +} + +bool dump_traffic(connection_t *c) { + for splay_each(node_t, n, node_tree) + send_request(c, "%d %d %s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, CONTROL, REQ_DUMP_TRAFFIC, + n->name, n->in_packets, n->in_bytes, n->out_packets, n->out_bytes); + + return send_request(c, "%d %d", CONTROL, REQ_DUMP_TRAFFIC); } diff --git a/src/node.h b/src/node.h index b360fe5..1b33789 100644 --- a/src/node.h +++ b/src/node.h @@ -3,7 +3,7 @@ /* node.h -- header for node.c - Copyright (C) 2001-2016 Guus Sliepen , + Copyright (C) 2001-2013 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,76 +21,103 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "avl_tree.h" +#include "splay_tree.h" +#include "cipher.h" #include "connection.h" +#include "digest.h" #include "event.h" #include "subnet.h" typedef struct node_status_t { unsigned int unused_active: 1; /* 1 if active (not used for nodes) */ unsigned int validkey: 1; /* 1 if we currently have a valid key for him */ - unsigned int unused_waitingforkey: 1; /* 1 if we already sent out a request */ + unsigned int waitingforkey: 1; /* 1 if we already sent out a request */ unsigned int visited: 1; /* 1 if this node has been visited by one of the graph algorithms */ unsigned int reachable: 1; /* 1 if this node is reachable in the graph */ unsigned int indirect: 1; /* 1 if this node is not directly reachable by us */ - unsigned int unused: 26; + unsigned int sptps: 1; /* 1 if this node supports SPTPS */ + unsigned int udp_confirmed: 1; /* 1 if the address is one that we received UDP traffic on */ + unsigned int send_locally: 1; /* 1 if the next UDP packet should be sent on the local network */ + unsigned int udppacket: 1; /* 1 if the most recently received packet was UDP */ + unsigned int validkey_in: 1; /* 1 if we have sent a valid key to him */ + unsigned int has_address: 1; /* 1 if we know an external address for this node */ + unsigned int ping_sent: 1; /* 1 if we sent a UDP probe but haven't received the reply yet */ + unsigned int unused: 19; } node_status_t; typedef struct node_t { char *name; /* name of this node */ + char *hostname; /* the hostname of its real ip */ + node_id_t id; /* unique node ID (name hash) */ uint32_t options; /* options turned on for this node */ int sock; /* Socket to use for outgoing UDP packets */ sockaddr_t address; /* his real (internet) ip to send UDP packets to */ - char *hostname; /* the hostname of its real ip */ node_status_t status; + time_t last_state_change; time_t last_req_key; - const EVP_CIPHER *incipher; /* Cipher type for UDP packets received from him */ - char *inkey; /* Cipher key and iv */ - int inkeylength; /* Cipher key and iv length */ - EVP_CIPHER_CTX *inctx; /* Cipher context */ + ecdsa_t *ecdsa; /* His public ECDSA key */ + sptps_t sptps; - const EVP_CIPHER *outcipher; /* Cipher type for UDP packets sent to him*/ - char *outkey; /* Cipher key and iv */ - int outkeylength; /* Cipher key and iv length */ - EVP_CIPHER_CTX *outctx; /* Cipher context */ +#ifndef DISABLE_LEGACY + cipher_t *incipher; /* Cipher for UDP packets */ + digest_t *indigest; /* Digest for UDP packets */ - const EVP_MD *indigest; /* Digest type for MAC of packets received from him */ - int inmaclength; /* Length of MAC */ - - const EVP_MD *outdigest; /* Digest type for MAC of packets sent to him*/ - int outmaclength; /* Length of MAC */ + cipher_t *outcipher; /* Cipher for UDP packets */ + digest_t *outdigest; /* Digest for UDP packets */ +#endif int incompression; /* Compressionlevel, 0 = no compression */ int outcompression; /* Compressionlevel, 0 = no compression */ + int distance; struct node_t *nexthop; /* nearest node from us to him */ struct edge_t *prevedge; /* nearest node from him to us */ struct node_t *via; /* next hop for UDP packets */ - avl_tree_t *subnet_tree; /* Pointer to a tree of subnets belonging to this node */ + splay_tree_t *subnet_tree; /* Pointer to a tree of subnets belonging to this node */ - avl_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ + splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */ uint32_t sent_seqno; /* Sequence number last sent to this node */ uint32_t received_seqno; /* Sequence number last received from this node */ + uint32_t received; /* Total valid packets received from this node */ + uint32_t prev_received_seqno; + uint32_t prev_received; uint32_t farfuture; /* Packets in a row that have arrived from the far future */ unsigned char *late; /* Bitfield marking late packets */ + struct timeval udp_reply_sent; /* Last time a (gratuitous) UDP probe reply was sent */ + struct timeval udp_ping_sent; /* Last time a UDP probe was sent */ + int udp_ping_rtt; /* Round trip time of UDP ping (in microseconds; or -1 if !status.udp_confirmed) */ + timeout_t udp_ping_timeout; /* Ping timeout event */ + + struct timeval mtu_ping_sent; /* Last time a MTU probe was sent */ + + struct timeval mtu_info_sent; /* Last time a MTU_INFO message was sent */ + struct timeval udp_info_sent; /* Last time a UDP_INFO message was sent */ + + length_t maxrecentlen; /* Maximum size of recently received packets */ + length_t mtu; /* Maximum size of packets to send to this node */ length_t minmtu; /* Probed minimum MTU */ length_t maxmtu; /* Probed maximum MTU */ int mtuprobes; /* Number of probes */ - event_t *mtuevent; /* Probe event */ + + uint64_t in_packets; + uint64_t in_bytes; + uint64_t out_packets; + uint64_t out_bytes; + + struct address_cache_t *address_cache; } node_t; extern struct node_t *myself; -extern avl_tree_t *node_tree; -extern avl_tree_t *node_udp_tree; +extern splay_tree_t *node_tree; extern void init_nodes(void); extern void exit_nodes(void); @@ -99,8 +126,10 @@ extern void free_node(node_t *n); extern void node_add(node_t *n); extern void node_del(node_t *n); extern node_t *lookup_node(char *name); +extern node_t *lookup_node_id(const node_id_t *id); extern node_t *lookup_node_udp(const sockaddr_t *sa); +extern bool dump_nodes(struct connection_t *c); +extern bool dump_traffic(struct connection_t *c); extern void update_node_udp(node_t *n, const sockaddr_t *sa); -extern void dump_nodes(void); #endif diff --git a/src/nolegacy/crypto.c b/src/nolegacy/crypto.c new file mode 100644 index 0000000..73c4916 --- /dev/null +++ b/src/nolegacy/crypto.c @@ -0,0 +1,96 @@ +/* + crypto.c -- Cryptographic miscellaneous functions and initialisation + Copyright (C) 2007-2021 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 "../crypto.h" + +#ifndef HAVE_MINGW + +static int random_fd = -1; + +static void random_init(void) { + random_fd = open("/dev/urandom", O_RDONLY); + + if(random_fd < 0) { + random_fd = open("/dev/random", O_RDONLY); + } + + if(random_fd < 0) { + fprintf(stderr, "Could not open source of random numbers: %s\n", strerror(errno)); + abort(); + } +} + +static void random_exit(void) { + close(random_fd); +} + +void randomize(void *vout, size_t outlen) { + char *out = vout; + + while(outlen) { + ssize_t len = read(random_fd, out, outlen); + + if(len <= 0) { + if(len == -1 && (errno == EAGAIN || errno == EINTR)) { + continue; + } + + fprintf(stderr, "Could not read random numbers: %s\n", strerror(errno)); + abort(); + } + + out += len; + outlen -= len; + } +} + +#else + +#include +HCRYPTPROV prov; + +void random_init(void) { + if(!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + fprintf(stderr, "CryptAcquireContext() failed!\n"); + abort(); + } +} + +void random_exit(void) { + CryptReleaseContext(prov, 0); +} + +void randomize(void *out, size_t outlen) { + if(!CryptGenRandom(prov, outlen, out)) { + fprintf(stderr, "CryptGenRandom() failed\n"); + abort(); + } +} + +#endif + +void crypto_init(void) { + random_init(); +} + +void crypto_exit(void) { + random_exit(); +} diff --git a/src/nolegacy/prf.c b/src/nolegacy/prf.c new file mode 100644 index 0000000..ce80f8c --- /dev/null +++ b/src/nolegacy/prf.c @@ -0,0 +1,121 @@ +/* + prf.c -- Pseudo-Random Function for key material generation + Copyright (C) 2011-2013 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 "../prf.h" +#include "../ed25519/sha512.h" + +static void memxor(char *buf, char c, size_t len) { + for(size_t i = 0; i < len; i++) { + buf[i] ^= c; + } +} + +static const size_t mdlen = 64; +static const size_t blklen = 128; + +static bool hmac_sha512(const char *key, size_t keylen, const char *msg, size_t msglen, char *out) { + char tmp[blklen + mdlen]; + sha512_context md; + + if(keylen <= blklen) { + memcpy(tmp, key, keylen); + memset(tmp + keylen, 0, blklen - keylen); + } else { + if(sha512(key, keylen, tmp) != 0) { + return false; + } + + memset(tmp + mdlen, 0, blklen - mdlen); + } + + if(sha512_init(&md) != 0) { + return false; + } + + // ipad + memxor(tmp, 0x36, blklen); + + if(sha512_update(&md, tmp, blklen) != 0) { + return false; + } + + // message + if(sha512_update(&md, msg, msglen) != 0) { + return false; + } + + if(sha512_final(&md, tmp + blklen) != 0) { + return false; + } + + // opad + memxor(tmp, 0x36 ^ 0x5c, blklen); + + if(sha512(tmp, sizeof(tmp), out) != 0) { + return false; + } + + return true; +} + + +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5. + We use SHA512 instead of MD5 and SHA1. + */ + +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + /* Data is what the "inner" HMAC function processes. + It consists of the previous HMAC result plus the seed. + */ + + char data[mdlen + seedlen]; + memset(data, 0, mdlen); + memcpy(data + mdlen, seed, seedlen); + + char hash[mdlen]; + + while(outlen > 0) { + /* Inner HMAC */ + if(!hmac_sha512(secret, secretlen, data, sizeof(data), data)) { + return false; + } + + /* Outer HMAC */ + if(outlen >= mdlen) { + if(!hmac_sha512(secret, secretlen, data, sizeof(data), out)) { + return false; + } + + out += mdlen; + outlen -= mdlen; + } else { + if(!hmac_sha512(secret, secretlen, data, sizeof(data), hash)) { + return false; + } + + memcpy(out, hash, outlen); + out += outlen; + outlen = 0; + } + } + + return true; +} diff --git a/src/openssl/cipher.c b/src/openssl/cipher.c new file mode 100644 index 0000000..974fbeb --- /dev/null +++ b/src/openssl/cipher.c @@ -0,0 +1,215 @@ +/* + cipher.c -- Symmetric block cipher handling + Copyright (C) 2007-2017 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 +#include + +#include "../cipher.h" +#include "../logger.h" +#include "../xalloc.h" + +struct cipher { + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; +}; + +static cipher_t *cipher_open(const EVP_CIPHER *evp_cipher) { + cipher_t *cipher = xzalloc(sizeof(*cipher)); + cipher->cipher = evp_cipher; + cipher->ctx = EVP_CIPHER_CTX_new(); + + if(!cipher->ctx) { + abort(); + } + + return cipher; +} + +cipher_t *cipher_open_by_name(const char *name) { + const EVP_CIPHER *evp_cipher = EVP_get_cipherbyname(name); + + if(!evp_cipher) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher name '%s'!", name); + return NULL; + } + + return cipher_open(evp_cipher); +} + +cipher_t *cipher_open_by_nid(int nid) { + const EVP_CIPHER *evp_cipher = EVP_get_cipherbynid(nid); + + if(!evp_cipher) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher nid %d!", nid); + return NULL; + } + + return cipher_open(evp_cipher); +} + +void cipher_close(cipher_t *cipher) { + if(!cipher) { + return; + } + + EVP_CIPHER_CTX_free(cipher->ctx); + free(cipher); +} + +size_t cipher_keylength(const cipher_t *cipher) { + if(!cipher || !cipher->cipher) { + return 0; + } + + return EVP_CIPHER_key_length(cipher->cipher) + EVP_CIPHER_iv_length(cipher->cipher); +} + +uint64_t cipher_budget(const cipher_t *cipher) { + /* Hopefully some failsafe way to calculate the maximum amount of bytes to + send/receive with a given cipher before we might run into birthday paradox + attacks. Because we might use different modes, the block size of the mode + might be 1 byte. In that case, use the IV length. Ensure the whole thing + is limited to what can be represented with a 64 bits integer. + */ + + if(!cipher || !cipher->cipher) { + return UINT64_MAX; // NULL cipher + } + + int ivlen = EVP_CIPHER_iv_length(cipher->cipher); + int blklen = EVP_CIPHER_block_size(cipher->cipher); + int len = blklen > 1 ? blklen : ivlen > 1 ? ivlen : 8; + int bits = len * 4 - 1; + return bits < 64 ? UINT64_C(1) << bits : UINT64_MAX; +} + +size_t cipher_blocksize(const cipher_t *cipher) { + if(!cipher || !cipher->cipher) { + return 1; + } + + return EVP_CIPHER_block_size(cipher->cipher); +} + +bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) { + bool result; + + if(encrypt) { + result = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, (unsigned char *)key + EVP_CIPHER_key_length(cipher->cipher)); + } else { + result = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, (unsigned char *)key + EVP_CIPHER_key_length(cipher->cipher)); + } + + if(result) { + return true; + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_set_key_from_rsa(cipher_t *cipher, void *key, size_t len, bool encrypt) { + bool result; + + if(encrypt) { + result = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, (unsigned char *)key + len - EVP_CIPHER_key_length(cipher->cipher), (unsigned char *)key + len - EVP_CIPHER_iv_length(cipher->cipher) - EVP_CIPHER_key_length(cipher->cipher)); + } else { + result = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, (unsigned char *)key + len - EVP_CIPHER_key_length(cipher->cipher), (unsigned char *)key + len - EVP_CIPHER_iv_length(cipher->cipher) - EVP_CIPHER_key_length(cipher->cipher)); + } + + if(result) { + return true; + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + if(oneshot) { + int len, pad; + + if(EVP_EncryptInit_ex(cipher->ctx, NULL, NULL, NULL, NULL) + && EVP_EncryptUpdate(cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) + && EVP_EncryptFinal_ex(cipher->ctx, (unsigned char *)outdata + len, &pad)) { + if(outlen) { + *outlen = len + pad; + } + + return true; + } + } else { + int len; + + if(EVP_EncryptUpdate(cipher->ctx, outdata, &len, indata, inlen)) { + if(outlen) { + *outlen = len; + } + + return true; + } + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + if(oneshot) { + int len, pad; + + if(EVP_DecryptInit_ex(cipher->ctx, NULL, NULL, NULL, NULL) + && EVP_DecryptUpdate(cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) + && EVP_DecryptFinal_ex(cipher->ctx, (unsigned char *)outdata + len, &pad)) { + if(outlen) { + *outlen = len + pad; + } + + return true; + } + } else { + int len; + + if(EVP_DecryptUpdate(cipher->ctx, outdata, &len, indata, inlen)) { + if(outlen) { + *outlen = len; + } + + return true; + } + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +int cipher_get_nid(const cipher_t *cipher) { + if(!cipher || !cipher->cipher) { + return 0; + } + + return EVP_CIPHER_nid(cipher->cipher); +} + +bool cipher_active(const cipher_t *cipher) { + return cipher && cipher->cipher && EVP_CIPHER_nid(cipher->cipher) != 0; +} diff --git a/src/openssl/crypto.c b/src/openssl/crypto.c new file mode 100644 index 0000000..5c75736 --- /dev/null +++ b/src/openssl/crypto.c @@ -0,0 +1,117 @@ +/* + crypto.c -- Cryptographic miscellaneous functions and initialisation + Copyright (C) 2007-2021 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 +#include + +#include "../crypto.h" + +#ifndef HAVE_MINGW + +static int random_fd = -1; + +static void random_init(void) { + random_fd = open("/dev/urandom", O_RDONLY); + + if(random_fd < 0) { + random_fd = open("/dev/random", O_RDONLY); + } + + if(random_fd < 0) { + fprintf(stderr, "Could not open source of random numbers: %s\n", strerror(errno)); + abort(); + } +} + +static void random_exit(void) { + close(random_fd); +} + +void randomize(void *vout, size_t outlen) { + char *out = vout; + + while(outlen) { + ssize_t len = read(random_fd, out, outlen); + + if(len <= 0) { + if(len == -1 && (errno == EAGAIN || errno == EINTR)) { + continue; + } + + fprintf(stderr, "Could not read random numbers: %s\n", strerror(errno)); + abort(); + } + + out += len; + outlen -= len; + } +} + +#else + +#include +HCRYPTPROV prov; + +void random_init(void) { + if(!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + fprintf(stderr, "CryptAcquireContext() failed!\n"); + abort(); + } +} + +void random_exit(void) { + CryptReleaseContext(prov, 0); +} + +void randomize(void *out, size_t outlen) { + if(!CryptGenRandom(prov, outlen, out)) { + fprintf(stderr, "CryptGenRandom() failed\n"); + abort(); + } +} + +#endif + +void crypto_init(void) { + random_init(); + + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); +#if OPENSSL_API_COMPAT < 0x10100000L + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +#endif + + if(!RAND_status()) { + fprintf(stderr, "Not enough entropy for the PRNG!\n"); + abort(); + } +} + +void crypto_exit(void) { +#if OPENSSL_API_COMPAT < 0x10100000L + EVP_cleanup(); + ERR_free_strings(); + ENGINE_cleanup(); +#endif + random_exit(); +} diff --git a/src/openssl/digest.c b/src/openssl/digest.c new file mode 100644 index 0000000..9569f3c --- /dev/null +++ b/src/openssl/digest.c @@ -0,0 +1,171 @@ +/* + digest.c -- Digest handling + Copyright (C) 2007-2016 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 "../utils.h" +#include "../xalloc.h" + +#include +#include + +#include "digest.h" +#include "../digest.h" +#include "../logger.h" + +static digest_t *digest_open(const EVP_MD *evp_md, int maclength) { + digest_t *digest = xzalloc(sizeof(*digest)); + digest->digest = evp_md; + + int digestlen = EVP_MD_size(digest->digest); + + if(maclength > digestlen || maclength < 0) { + digest->maclength = digestlen; + } else { + digest->maclength = maclength; + } + + return digest; +} + +digest_t *digest_open_by_name(const char *name, int maclength) { + const EVP_MD *evp_md = EVP_get_digestbyname(name); + + if(!evp_md) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest name '%s'!", name); + return false; + } + + return digest_open(evp_md, maclength); +} + +digest_t *digest_open_by_nid(int nid, int maclength) { + const EVP_MD *evp_md = EVP_get_digestbynid(nid); + + if(!evp_md) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest nid %d!", nid); + return false; + } + + return digest_open(evp_md, maclength); +} + +bool digest_set_key(digest_t *digest, const void *key, size_t len) { +#ifdef HAVE_HMAC_CTX_NEW + digest->hmac_ctx = HMAC_CTX_new(); + HMAC_Init_ex(digest->hmac_ctx, key, len, digest->digest, NULL); +#else + digest->hmac_ctx = xzalloc(sizeof(*digest->hmac_ctx)); + HMAC_Init(digest->hmac_ctx, key, len, digest->digest); +#endif + + if(!digest->hmac_ctx) { + abort(); + } + + return true; +} + +void digest_close(digest_t *digest) { + if(!digest) { + return; + } + + if(digest->md_ctx) { + EVP_MD_CTX_destroy(digest->md_ctx); + } + +#ifdef HAVE_HMAC_CTX_NEW + + if(digest->hmac_ctx) { + HMAC_CTX_free(digest->hmac_ctx); + } + +#else + free(digest->hmac_ctx); +#endif + + free(digest); +} + +bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) { + size_t len = EVP_MD_size(digest->digest); + unsigned char tmpdata[len]; + + if(digest->hmac_ctx) { + if(!HMAC_Init_ex(digest->hmac_ctx, NULL, 0, NULL, NULL) + || !HMAC_Update(digest->hmac_ctx, indata, inlen) + || !HMAC_Final(digest->hmac_ctx, tmpdata, NULL)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Error creating digest: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + } else { + if(!digest->md_ctx) { + digest->md_ctx = EVP_MD_CTX_create(); + } + + if(!digest->md_ctx) { + abort(); + } + + if(!EVP_DigestInit(digest->md_ctx, digest->digest) + || !EVP_DigestUpdate(digest->md_ctx, indata, inlen) + || !EVP_DigestFinal(digest->md_ctx, tmpdata, NULL)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Error creating digest: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + } + + memcpy(outdata, tmpdata, digest->maclength); + return true; +} + +bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *cmpdata) { + size_t len = digest->maclength; + unsigned char outdata[len]; + + return digest_create(digest, indata, inlen, outdata) && !memcmp(cmpdata, outdata, digest->maclength); +} + +int digest_get_nid(const digest_t *digest) { + if(!digest || !digest->digest) { + return 0; + } + + return EVP_MD_type(digest->digest); +} + +size_t digest_keylength(const digest_t *digest) { + if(!digest || !digest->digest) { + return 0; + } + + return EVP_MD_size(digest->digest); +} + +size_t digest_length(const digest_t *digest) { + if(!digest) { + return 0; + } + + return digest->maclength; +} + +bool digest_active(const digest_t *digest) { + return digest && digest->digest && EVP_MD_type(digest->digest) != 0; +} diff --git a/src/openssl/digest.h b/src/openssl/digest.h new file mode 100644 index 0000000..420b11e --- /dev/null +++ b/src/openssl/digest.h @@ -0,0 +1,33 @@ +#ifndef TINC_OPENSSL_DIGEST_H +#define TINC_OPENSSL_DIGEST_H + +/* + digest.h -- header file digest.c + Copyright (C) 2013 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 +#include + +struct digest { + const EVP_MD *digest; + HMAC_CTX *hmac_ctx; + EVP_MD_CTX *md_ctx; + int maclength; +}; + +#endif diff --git a/src/openssl/prf.c b/src/openssl/prf.c new file mode 100644 index 0000000..37af2ef --- /dev/null +++ b/src/openssl/prf.c @@ -0,0 +1,86 @@ +/* + prf.c -- Pseudo-Random Function for key material generation + Copyright (C) 2011-2013 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 "digest.h" +#include "../digest.h" +#include "../prf.h" + +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5. + We use SHA512 instead of MD5 and SHA1. + */ + +static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + digest_t *digest = digest_open_by_nid(nid, -1); + + if(!digest) { + return false; + } + + if(!digest_set_key(digest, secret, secretlen)) { + digest_close(digest); + return false; + } + + size_t len = digest_length(digest); + + /* Data is what the "inner" HMAC function processes. + It consists of the previous HMAC result plus the seed. + */ + + char data[len + seedlen]; + memset(data, 0, len); + memcpy(data + len, seed, seedlen); + + char hash[len]; + + while(outlen > 0) { + /* Inner HMAC */ + if(!digest_create(digest, data, len + seedlen, data)) { + digest_close(digest); + return false; + } + + /* Outer HMAC */ + if(!digest_create(digest, data, len + seedlen, hash)) { + digest_close(digest); + return false; + } + + /* XOR the results of the outer HMAC into the out buffer */ + for(size_t i = 0; i < len && i < outlen; i++) { + *out++ ^= hash[i]; + } + + outlen -= len; + } + + digest_close(digest); + return true; +} + +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */ + memset(out, 0, outlen); + + return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen); +} diff --git a/src/openssl/rsa.c b/src/openssl/rsa.c new file mode 100644 index 0000000..f8ec6d5 --- /dev/null +++ b/src/openssl/rsa.c @@ -0,0 +1,147 @@ +/* + rsa.c -- RSA key handling + Copyright (C) 2007-2021 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 +#include + +#define TINC_RSA_INTERNAL +typedef RSA rsa_t; + +#include "../logger.h" +#include "../rsa.h" + +// Set RSA keys + +#ifndef HAVE_RSA_SET0_KEY +int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) { + BN_free(r->n); + r->n = n; + BN_free(r->e); + r->e = e; + BN_free(r->d); + r->d = d; + return 1; +} +#endif + +rsa_t *rsa_set_hex_public_key(char *n, char *e) { + BIGNUM *bn_n = NULL; + BIGNUM *bn_e = NULL; + + if((size_t)BN_hex2bn(&bn_n, n) != strlen(n) || (size_t)BN_hex2bn(&bn_e, e) != strlen(e)) { + BN_free(bn_e); + BN_free(bn_n); + return false; + } + + rsa_t *rsa = RSA_new(); + + if(!rsa) { + return NULL; + } + + RSA_set0_key(rsa, bn_n, bn_e, NULL); + + return rsa; +} + +rsa_t *rsa_set_hex_private_key(char *n, char *e, char *d) { + BIGNUM *bn_n = NULL; + BIGNUM *bn_e = NULL; + BIGNUM *bn_d = NULL; + + if((size_t)BN_hex2bn(&bn_n, n) != strlen(n) || (size_t)BN_hex2bn(&bn_e, e) != strlen(e) || (size_t)BN_hex2bn(&bn_d, d) != strlen(d)) { + BN_free(bn_d); + BN_free(bn_e); + BN_free(bn_n); + return false; + } + + rsa_t *rsa = RSA_new(); + + if(!rsa) { + return NULL; + } + + RSA_set0_key(rsa, bn_n, bn_e, bn_d); + + return rsa; +} + +// Read PEM RSA keys + +rsa_t *rsa_read_pem_public_key(FILE *fp) { + rsa_t *rsa = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL); + + if(!rsa) { + rewind(fp); + rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL); + } + + if(!rsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); + } + + return rsa; +} + +rsa_t *rsa_read_pem_private_key(FILE *fp) { + rsa_t *rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL); + + if(!rsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); + } + + return rsa; +} + +size_t rsa_size(rsa_t *rsa) { + return RSA_size(rsa); +} + +bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) { + if((size_t)RSA_public_encrypt(len, in, out, rsa, RSA_NO_PADDING) == len) { + return true; + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to perform RSA encryption: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool rsa_private_decrypt(rsa_t *rsa, void *in, size_t len, void *out) { + if((size_t)RSA_private_decrypt(len, in, out, rsa, RSA_NO_PADDING) == len) { + return true; + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to perform RSA decryption: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool rsa_active(rsa_t *rsa) { + return rsa; +} + +void rsa_free(rsa_t *rsa) { + if(rsa) { + RSA_free(rsa); + } +} diff --git a/src/openssl/rsagen.c b/src/openssl/rsagen.c new file mode 100644 index 0000000..79127f6 --- /dev/null +++ b/src/openssl/rsagen.c @@ -0,0 +1,119 @@ +/* + rsagen.c -- RSA key generation and export + Copyright (C) 2008-2013 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 + +#define TINC_RSA_INTERNAL +typedef RSA rsa_t; + +#include "../logger.h" +#include "../rsagen.h" +#include "../xalloc.h" + +/* This function prettyprints the key generation process */ + +static int indicator(int a, int b, BN_GENCB *cb) { + (void)cb; + + switch(a) { + case 0: + fprintf(stderr, "."); + break; + + case 1: + fprintf(stderr, "+"); + break; + + case 2: + fprintf(stderr, "-"); + break; + + case 3: + switch(b) { + case 0: + fprintf(stderr, " p\n"); + break; + + case 1: + fprintf(stderr, " q\n"); + break; + + default: + fprintf(stderr, "?"); + } + + break; + + default: + fprintf(stderr, "?"); + } + + return 1; +} + +// Generate RSA key + +#ifndef HAVE_BN_GENCB_NEW +BN_GENCB *BN_GENCB_new(void) { + return xzalloc(sizeof(BN_GENCB)); +} + +void BN_GENCB_free(BN_GENCB *cb) { + free(cb); +} +#endif + +rsa_t *rsa_generate(size_t bits, unsigned long exponent) { + BIGNUM *bn_e = BN_new(); + rsa_t *rsa = RSA_new(); + BN_GENCB *cb = BN_GENCB_new(); + + if(!bn_e || !rsa || !cb) { + abort(); + } + + BN_set_word(bn_e, exponent); + BN_GENCB_set(cb, indicator, NULL); + + int result = RSA_generate_key_ex(rsa, bits, bn_e, cb); + + BN_GENCB_free(cb); + BN_free(bn_e); + + if(!result) { + fprintf(stderr, "Error during key generation!\n"); + RSA_free(rsa); + return NULL; + } + + return rsa; +} + +// Write PEM RSA keys + +bool rsa_write_pem_public_key(rsa_t *rsa, FILE *fp) { + return PEM_write_RSAPublicKey(fp, rsa); +} + +bool rsa_write_pem_private_key(rsa_t *rsa, FILE *fp) { + return PEM_write_RSAPrivateKey(fp, rsa, NULL, NULL, 0, NULL, NULL); +} diff --git a/src/pidfile.c b/src/pidfile.c deleted file mode 100644 index dd17267..0000000 --- a/src/pidfile.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - pidfile.c - interact with pidfiles - Copyright (c) 1995 Martin Schulze - - This file is part of the sysklogd package, a kernel and system log daemon. - - 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. -*/ - -/* left unaltered for tinc -- Ivo Timmermans */ -/* - * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze - * First version (v0.2) released - */ - -#include "system.h" - -#include "pidfile.h" - -#ifndef HAVE_MINGW -/* read_pid - * - * Reads the specified pidfile and returns the read pid. - * 0 is returned if either there's no pidfile, it's empty - * or no pid can be read. - */ -pid_t read_pid(const char *pidfile) { - FILE *f; - long pid; - - if(!(f = fopen(pidfile, "r"))) { - return 0; - } - - if(fscanf(f, "%20ld", &pid) != 1) { - pid = 0; - } - - fclose(f); - return (pid_t)pid; -} - -/* check_pid - * - * Reads the pid using read_pid and looks up the pid in the process - * table (using /proc) to determine if the process already exists. If - * so the pid is returned, otherwise 0. - */ -pid_t check_pid(const char *pidfile) { - pid_t pid = read_pid(pidfile); - - /* Amazing ! _I_ am already holding the pid file... */ - if((!pid) || (pid == getpid())) { - return 0; - } - - /* - * The 'standard' method of doing this is to try and do a 'fake' kill - * of the process. If an ESRCH error is returned the process cannot - * be found -- GW - */ - /* But... errno is usually changed only on error.. */ - errno = 0; - - if(kill(pid, 0) && errno == ESRCH) { - return 0; - } - - return pid; -} - -/* write_pid - * - * Writes the pid to the specified file. If that fails 0 is - * returned, otherwise the pid. - */ -pid_t write_pid(const char *pidfile) { - FILE *f; - int fd; - pid_t pid; - - if((fd = open(pidfile, O_RDWR | O_CREAT, 0644)) == -1) { - return 0; - } - - if((f = fdopen(fd, "r+")) == NULL) { - close(fd); - return 0; - } - -#ifdef HAVE_FLOCK - - if(flock(fd, LOCK_EX | LOCK_NB) == -1) { - fclose(f); - return 0; - } - -#endif - - pid = getpid(); - - if(!fprintf(f, "%ld\n", (long)pid)) { - fclose(f); - return 0; - } - - fflush(f); - -#ifdef HAVE_FLOCK - - if(flock(fd, LOCK_UN) == -1) { - fclose(f); - return 0; - } - -#endif - fclose(f); - - return pid; -} - -/* remove_pid - * - * Remove the the specified file. The result from unlink(2) - * is returned - */ -int remove_pid(const char *pidfile) { - return unlink(pidfile); -} -#endif diff --git a/src/pidfile.h b/src/pidfile.h deleted file mode 100644 index 7d71cc2..0000000 --- a/src/pidfile.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef TINC_PIDFILE_H -#define TINC_PIDFILE_H - -/* - pidfile.h - interact with pidfiles - Copyright (c) 1995 Martin Schulze - - This file is part of the sysklogd package, a kernel and system log daemon. - - 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. -*/ - -#ifndef HAVE_MINGW -/* read_pid - * - * Reads the specified pidfile and returns the read pid. - * 0 is returned if either there's no pidfile, it's empty - * or no pid can be read. - */ -extern pid_t read_pid(const char *pidfile); - -/* check_pid - * - * Reads the pid using read_pid and looks up the pid in the process - * table (using /proc) to determine if the process already exists. If - * so 1 is returned, otherwise 0. - */ -extern pid_t check_pid(const char *pidfile); - -/* write_pid - * - * Writes the pid to the specified file. If that fails 0 is - * returned, otherwise the pid. - */ -extern pid_t write_pid(const char *pidfile); - -/* remove_pid - * - * Remove the the specified file. The result from unlink(2) - * is returned - */ -extern int remove_pid(const char *pidfile); -#endif - -#endif diff --git a/src/prf.h b/src/prf.h new file mode 100644 index 0000000..9e64989 --- /dev/null +++ b/src/prf.h @@ -0,0 +1,25 @@ +#ifndef TINC_PRF_H +#define TINC_PRF_H + +/* + prf.h -- header file for prf.c + Copyright (C) 2011-2013 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. +*/ + +extern bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/process.c b/src/process.c index 13d1007..b6d4e12 100644 --- a/src/process.c +++ b/src/process.c @@ -1,7 +1,7 @@ /* process.c -- process management functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2015 Guus Sliepen + 2000-2018 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 @@ -22,67 +22,55 @@ #include "conf.h" #include "connection.h" +#include "control.h" #include "device.h" #include "edge.h" +#include "event.h" #include "logger.h" +#include "names.h" #include "net.h" #include "node.h" -#include "pidfile.h" #include "process.h" #include "subnet.h" #include "utils.h" #include "xalloc.h" +#include "version.h" /* If zero, don't detach from the terminal. */ bool do_detach = true; -bool sighup = false; bool sigalrm = false; -extern char *identname; -extern char *pidfilename; extern char **g_argv; extern bool use_logfile; - -#ifndef HAVE_MINGW -static sigset_t emptysigset; -#endif +extern bool use_syslog; /* Some functions the less gifted operating systems might lack... */ #ifdef HAVE_MINGW -extern char *identname; -extern char *program_name; -extern char **g_argv; - static SC_HANDLE manager = NULL; static SC_HANDLE service = NULL; static SERVICE_STATUS status = {0}; static SERVICE_STATUS_HANDLE statushandle = 0; -bool install_service(void) { +static bool install_service(void) { char command[4096] = "\""; - char **argp; - bool space; SERVICE_DESCRIPTION description = {"Virtual Private Network daemon"}; manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(!manager) { - logger(LOG_ERR, "Could not open service manager: %s", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open service manager: %s", winerror(GetLastError())); return false; } - if(!strchr(program_name, '\\')) { - GetCurrentDirectory(sizeof(command) - 1, command + 1); - strncat(command, "\\", sizeof(command) - strlen(command)); - } - - strncat(command, program_name, sizeof(command) - strlen(command)); + HMODULE module = GetModuleHandle(NULL); + GetModuleFileName(module, command + 1, sizeof(command) - 1); + command[sizeof(command) - 1] = 0; strncat(command, "\"", sizeof(command) - strlen(command)); - for(argp = g_argv + 1; *argp; argp++) { - space = strchr(*argp, ' '); + for(char **argp = g_argv + 1; *argp; argp++) { + char *space = strchr(*argp, ' '); strncat(command, " ", sizeof(command) - strlen(command)); if(space) { @@ -102,7 +90,7 @@ bool install_service(void) { if(!service) { DWORD lasterror = GetLastError(); - logger(LOG_ERR, "Could not create %s service: %s", identname, winerror(lasterror)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create %s service: %s", identname, winerror(lasterror)); if(lasterror != ERROR_SERVICE_EXISTS) { return false; @@ -111,83 +99,54 @@ bool install_service(void) { if(service) { ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &description); - logger(LOG_INFO, "%s service installed", identname); + logger(DEBUG_ALWAYS, LOG_INFO, "%s service installed", identname); } else { service = OpenService(manager, identname, SERVICE_ALL_ACCESS); } if(!StartService(service, 0, NULL)) { - logger(LOG_WARNING, "Could not start %s service: %s", identname, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not start %s service: %s", identname, winerror(GetLastError())); } else { - logger(LOG_INFO, "%s service started", identname); + logger(DEBUG_ALWAYS, LOG_INFO, "%s service started", identname); } return true; } -bool remove_service(void) { - manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); +io_t stop_io; - if(!manager) { - logger(LOG_ERR, "Could not open service manager: %s", winerror(GetLastError())); - return false; - } +DWORD WINAPI controlhandler(DWORD request, DWORD type, LPVOID data, LPVOID context) { + (void)type; + (void)data; + (void)context; - service = OpenService(manager, identname, SERVICE_ALL_ACCESS); - - if(!service) { - logger(LOG_ERR, "Could not open %s service: %s", identname, winerror(GetLastError())); - return false; - } - - if(!ControlService(service, SERVICE_CONTROL_STOP, &status)) { - logger(LOG_ERR, "Could not stop %s service: %s", identname, winerror(GetLastError())); - } else { - logger(LOG_INFO, "%s service stopped", identname); - } - - if(!DeleteService(service)) { - logger(LOG_ERR, "Could not remove %s service: %s", identname, winerror(GetLastError())); - return false; - } - - logger(LOG_INFO, "%s service removed", identname); - - return true; -} - -DWORD WINAPI controlhandler(DWORD request, DWORD type, LPVOID boe, LPVOID bah) { switch(request) { case SERVICE_CONTROL_INTERROGATE: SetServiceStatus(statushandle, &status); return NO_ERROR; case SERVICE_CONTROL_STOP: - logger(LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_STOP"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_STOP"); break; case SERVICE_CONTROL_SHUTDOWN: - logger(LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_SHUTDOWN"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_SHUTDOWN"); break; default: - logger(LOG_WARNING, "Got unexpected request %d", (int)request); + logger(DEBUG_ALWAYS, LOG_WARNING, "Got unexpected request %d", (int)request); return ERROR_CALL_NOT_IMPLEMENTED; } - if(running) { - running = false; - status.dwWaitHint = 30000; - status.dwCurrentState = SERVICE_STOP_PENDING; - SetServiceStatus(statushandle, &status); - return NO_ERROR; - } else { - status.dwWaitHint = 0; - status.dwCurrentState = SERVICE_STOPPED; - SetServiceStatus(statushandle, &status); - exit(1); + status.dwWaitHint = 1000; + status.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(statushandle, &status); + + if(WSASetEvent(stop_io.event) == FALSE) { + abort(); } + return NO_ERROR; } VOID WINAPI run_service(DWORD argc, LPTSTR *argv) { @@ -202,7 +161,7 @@ VOID WINAPI run_service(DWORD argc, LPTSTR *argv) { statushandle = RegisterServiceCtrlHandlerEx(identname, controlhandler, NULL); if(!statushandle) { - logger(LOG_ERR, "System call `%s' failed: %s", "RegisterServiceCtrlHandlerEx", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "RegisterServiceCtrlHandlerEx", winerror(GetLastError())); } else { status.dwWaitHint = 30000; status.dwCurrentState = SERVICE_START_PENDING; @@ -232,7 +191,7 @@ bool init_service(void) { if(GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { return false; } else { - logger(LOG_ERR, "System call `%s' failed: %s", "StartServiceCtrlDispatcher", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "StartServiceCtrlDispatcher", winerror(GetLastError())); } } @@ -240,92 +199,17 @@ bool init_service(void) { } #endif -#ifndef HAVE_MINGW /* - check for an existing tinc for this net, and write pid to pidfile -*/ -static bool write_pidfile(void) { - pid_t pid; - - pid = check_pid(pidfilename); - - if(pid) { - if(netname) - fprintf(stderr, "A tincd is already running for net `%s' with pid %ld.\n", - netname, (long)pid); - else { - fprintf(stderr, "A tincd is already running with pid %ld.\n", (long)pid); - } - - return false; - } - - /* if it's locked, write-protected, or whatever */ - if(!write_pid(pidfilename)) { - fprintf(stderr, "Couldn't write pid file %s: %s\n", pidfilename, strerror(errno)); - return false; - } - - return true; -} -#endif - -/* - kill older tincd for this net -*/ -bool kill_other(int signal) { -#ifndef HAVE_MINGW - pid_t pid; - - pid = read_pid(pidfilename); - - if(!pid) { - if(netname) - fprintf(stderr, "No other tincd is running for net `%s'.\n", - netname); - else { - fprintf(stderr, "No other tincd is running.\n"); - } - - return false; - } - - errno = 0; /* No error, sometimes errno is only changed on error */ - - /* ESRCH is returned when no process with that pid is found */ - if(kill(pid, signal) && errno == ESRCH) { - if(netname) - fprintf(stderr, "The tincd for net `%s' is no longer running. ", - netname); - else { - fprintf(stderr, "The tincd is no longer running. "); - } - - fprintf(stderr, "Removing stale lock file.\n"); - remove_pid(pidfilename); - } - - return true; -#else - return remove_service(); -#endif -} - -/* - Detach from current terminal, write pidfile, kill parent + Detach from current terminal */ bool detach(void) { - setup_signals(); - - /* First check if we can open a fresh new pidfile */ + logmode_t logmode; #ifndef HAVE_MINGW - - if(!write_pidfile()) { - return false; - } - - /* If we succeeded in doing that, detach */ + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + signal(SIGUSR2, SIG_IGN); + signal(SIGWINCH, SIG_IGN); closelogger(); #endif @@ -333,346 +217,32 @@ bool detach(void) { if(do_detach) { #ifndef HAVE_MINGW - if(daemon(0, 0)) { - fprintf(stderr, "Couldn't detach from terminal: %s", - strerror(errno)); - return false; - } - - /* Now UPDATE the pid in the pidfile, because we changed it... */ - - if(!write_pid(pidfilename)) { - fprintf(stderr, "Could not write pid file %s: %s\n", pidfilename, strerror(errno)); + if(daemon(1, 0)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Couldn't detach from terminal: %s", strerror(errno)); return false; } #else if(!statushandle) { - exit(install_service()); + exit(!install_service()); } #endif } - openlogger(identname, use_logfile ? LOGMODE_FILE : (do_detach ? LOGMODE_SYSLOG : LOGMODE_STDERR)); + if(use_logfile) { + logmode = LOGMODE_FILE; + } else if(use_syslog || do_detach) { + logmode = LOGMODE_SYSLOG; + } else { + logmode = LOGMODE_STDERR; + } - logger(LOG_NOTICE, "tincd %s starting, debug level %d", - VERSION, debug_level); + openlogger(identname, logmode); + + logger(DEBUG_ALWAYS, LOG_NOTICE, "tincd %s (%s %s) starting, debug level %d", + BUILD_VERSION, BUILD_DATE, BUILD_TIME, debug_level); return true; } - -#ifdef HAVE_PUTENV -void unputenv(char *p) { - char *e = strchr(p, '='); - - if(!e) { - return; - } - - int len = e - p; -#ifndef HAVE_UNSETENV -#ifdef HAVE_MINGW - // Windows requires putenv("FOO=") to unset %FOO% - len++; -#endif -#endif - char var[len + 1]; - memcpy(var, p, len); - var[len] = 0; -#ifdef HAVE_UNSETENV - unsetenv(var); -#else - // We must keep what we putenv() around in memory. - // To do this without memory leaks, keep things in a list and reuse if possible. - static list_t list = {}; - - for(list_node_t *node = list.head; node; node = node->next) { - char *data = node->data; - - if(!strcmp(data, var)) { - putenv(data); - return; - } - } - - char *data = xstrdup(var); - list_insert_tail(&list, data); - putenv(data); -#endif -} -#else -void putenv(const char *p) {} -void unputenv(const char *p) {} -#endif - -bool execute_script(const char *name, char **envp) { -#ifdef HAVE_SYSTEM - char *scriptname; - char *interpreter = NULL; - config_t *cfg_interpreter; - int status, len, i; - - cfg_interpreter = lookup_config(config_tree, "ScriptsInterpreter"); -#ifndef HAVE_MINGW - len = xasprintf(&scriptname, "\"%s/%s\"", confbase, name); -#else - - if(cfg_interpreter) { - len = xasprintf(&scriptname, "\"%s/%s\"", confbase, name); - } else { - len = xasprintf(&scriptname, "\"%s/%s.bat\"", confbase, name); - } - -#endif - - if(len < 0) { - return false; - } - - scriptname[len - 1] = '\0'; - - /* First check if there is a script */ - if(access(scriptname + 1, F_OK)) { - free(scriptname); - return true; - } - - // Custom scripts interpreter - if(get_config_string(cfg_interpreter, &interpreter)) { - // Force custom scripts interpreter allowing execution of scripts on android without execution flag (such as on /sdcard) - free(scriptname); - len = xasprintf(&scriptname, "%s \"%s/%s\"", interpreter, confbase, name); - free(interpreter); - - if(len < 0) { - return false; - } - } - - ifdebug(STATUS) logger(LOG_INFO, "Executing script %s", name); - - /* Set environment */ - - for(i = 0; envp[i]; i++) { - putenv(envp[i]); - } - - scriptname[len - 1] = '\"'; - status = system(scriptname); - - free(scriptname); - - /* Unset environment */ - - for(i = 0; envp[i]; i++) { - unputenv(envp[i]); - } - - if(status != -1) { -#ifdef WEXITSTATUS - - if(WIFEXITED(status)) { /* Child exited by itself */ - if(WEXITSTATUS(status)) { - logger(LOG_ERR, "Script %s exited with non-zero status %d", - name, WEXITSTATUS(status)); - return false; - } - } else if(WIFSIGNALED(status)) { /* Child was killed by a signal */ - logger(LOG_ERR, "Script %s was killed by signal %d (%s)", - name, WTERMSIG(status), strsignal(WTERMSIG(status))); - return false; - } else { /* Something strange happened */ - logger(LOG_ERR, "Script %s terminated abnormally", name); - return false; - } - -#endif - } else { - logger(LOG_ERR, "System call `%s' failed: %s", "system", strerror(errno)); - return false; - } - -#endif - return true; -} - - -/* - Signal handlers. -*/ - -#ifndef HAVE_MINGW -static RETSIGTYPE sigterm_handler(int a) { - (void)a; - logger(LOG_NOTICE, "Got %s signal", "TERM"); - - if(running) { - running = false; - } else { - exit(1); - } -} - -static RETSIGTYPE sigquit_handler(int a) { - (void)a; - logger(LOG_NOTICE, "Got %s signal", "QUIT"); - - if(running) { - running = false; - } else { - exit(1); - } -} - -static RETSIGTYPE fatal_signal_square(int a) { - logger(LOG_ERR, "Got another fatal signal %d (%s): not restarting.", a, - strsignal(a)); - exit(1); -} - -static RETSIGTYPE fatal_signal_handler(int a) { - struct sigaction act; - logger(LOG_ERR, "Got fatal signal %d (%s)", a, strsignal(a)); - - if(do_detach) { - logger(LOG_NOTICE, "Trying to re-execute in 5 seconds..."); - - act.sa_handler = fatal_signal_square; - act.sa_mask = emptysigset; - act.sa_flags = 0; - sigaction(SIGSEGV, &act, NULL); - - close_network_connections(); - sleep(5); - remove_pid(pidfilename); - execvp(g_argv[0], g_argv); - } else { - logger(LOG_NOTICE, "Not restarting."); - exit(1); - } -} - -static RETSIGTYPE sighup_handler(int a) { - (void)a; - logger(LOG_NOTICE, "Got %s signal", "HUP"); - sighup = true; -} - -static RETSIGTYPE sigint_handler(int a) { - (void)a; - static int saved_debug_level = -1; - - logger(LOG_NOTICE, "Got %s signal", "INT"); - - if(saved_debug_level != -1) { - logger(LOG_NOTICE, "Reverting to old debug level (%d)", - saved_debug_level); - debug_level = saved_debug_level; - saved_debug_level = -1; - } else { - logger(LOG_NOTICE, - "Temporarily setting debug level to 5. Kill me with SIGINT again to go back to level %d.", - debug_level); - saved_debug_level = debug_level; - debug_level = 5; - } -} - -static RETSIGTYPE sigalrm_handler(int a) { - (void)a; - logger(LOG_NOTICE, "Got %s signal", "ALRM"); - sigalrm = true; -} - -static RETSIGTYPE sigusr1_handler(int a) { - (void)a; - dump_connections(); -} - -static RETSIGTYPE sigusr2_handler(int a) { - (void)a; - devops.dump_stats(); - dump_nodes(); - dump_edges(); - dump_subnets(); -} - -static RETSIGTYPE sigwinch_handler(int a) { - (void)a; - do_purge = true; -} - -static RETSIGTYPE unexpected_signal_handler(int a) { - (void)a; - logger(LOG_WARNING, "Got unexpected signal %d (%s)", a, strsignal(a)); -} - -static RETSIGTYPE ignore_signal_handler(int a) { - (void)a; - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Ignored signal %d (%s)", a, strsignal(a)); -} - -static struct { - int signal; - void (*handler)(int); -} sighandlers[] = { - {SIGHUP, sighup_handler}, - {SIGTERM, sigterm_handler}, - {SIGQUIT, sigquit_handler}, - {SIGSEGV, fatal_signal_handler}, - {SIGBUS, fatal_signal_handler}, - {SIGILL, fatal_signal_handler}, - {SIGPIPE, ignore_signal_handler}, - {SIGINT, sigint_handler}, - {SIGUSR1, sigusr1_handler}, - {SIGUSR2, sigusr2_handler}, - {SIGCHLD, ignore_signal_handler}, - {SIGALRM, sigalrm_handler}, - {SIGWINCH, sigwinch_handler}, - {SIGABRT, SIG_DFL}, - {0, NULL} -}; -#endif - -void setup_signals(void) { -#ifndef HAVE_MINGW - int i; - struct sigaction act; - - sigemptyset(&emptysigset); - act.sa_handler = NULL; - act.sa_mask = emptysigset; - act.sa_flags = 0; - - /* Set a default signal handler for every signal, errors will be - ignored. */ - for(i = 1; i < NSIG; i++) { - if(!do_detach) { - act.sa_handler = SIG_DFL; - } else { - act.sa_handler = unexpected_signal_handler; - } - - sigaction(i, &act, NULL); - } - - /* If we didn't detach, allow coredumps */ - if(!do_detach) { - sighandlers[3].handler = SIG_DFL; - } - - /* Then, for each known signal that we want to catch, assign a - handler to the signal, with error checking this time. */ - for(i = 0; sighandlers[i].signal; i++) { - act.sa_handler = sighandlers[i].handler; - - if(sigaction(sighandlers[i].signal, &act, NULL) < 0) - fprintf(stderr, "Installing signal handler for signal %d (%s) failed: %s\n", - sighandlers[i].signal, strsignal(sighandlers[i].signal), - strerror(errno)); - } - -#endif -} diff --git a/src/process.h b/src/process.h index e775ac3..93ef5e9 100644 --- a/src/process.h +++ b/src/process.h @@ -4,7 +4,7 @@ /* process.h -- header file for process.c Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2006 Guus Sliepen + 2000-2013 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 @@ -22,15 +22,14 @@ */ extern bool do_detach; -extern bool sighup; extern bool sigalrm; extern void setup_signals(void); -extern bool execute_script(const char *name, char **envp); extern bool detach(void); -extern bool kill_other(int signal); +extern bool kill_other(int); #ifdef HAVE_MINGW +extern io_t stop_io; extern bool init_service(void); #endif diff --git a/src/protocol.c b/src/protocol.c index 4f74d3b..d8b8867 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1,7 +1,7 @@ /* protocol.c -- handle the meta-protocol, basic functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2013 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 @@ -30,16 +30,20 @@ bool tunnelserver = false; bool strictsubnets = false; +bool experimental = true; /* Jumptable for the request handlers */ -static bool (*request_handlers[])(connection_t *) = { +static bool (*request_handlers[])(connection_t *, const char *) = { id_h, metakey_h, challenge_h, chal_reply_h, ack_h, - NULL, NULL, NULL, + NULL, NULL, termreq_h, ping_h, pong_h, add_subnet_h, del_subnet_h, add_edge_h, del_edge_h, - key_changed_h, req_key_h, ans_key_h, tcppacket_h, + key_changed_h, req_key_h, ans_key_h, tcppacket_h, control_h, + NULL, NULL, /* Not "real" requests (yet) */ + sptps_tcppacket_h, + udp_info_h, mtu_info_h, }; /* Request names */ @@ -49,123 +53,107 @@ static char (*request_name[]) = { "STATUS", "ERROR", "TERMREQ", "PING", "PONG", "ADD_SUBNET", "DEL_SUBNET", - "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", + "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", "CONTROL", + "REQ_PUBKEY", "ANS_PUBKEY", "SPTPS_PACKET", "UDP_INFO", "MTU_INFO", }; -static avl_tree_t *past_request_tree; - -bool check_id(const char *id) { - for(; *id; id++) - if(!isalnum(*id) && *id != '_') { - return false; - } - - return true; -} +static splay_tree_t *past_request_tree; /* Generic request routines - takes care of logging and error detection as well */ bool send_request(connection_t *c, const char *format, ...) { va_list args; - char buffer[MAXBUFSIZE]; - int len, request = 0; + char request[MAXBUFSIZE]; + int len; /* Use vsnprintf instead of vxasprintf: faster, no memory fragmentation, cleanup is automatic, and there is a limit on the input buffer anyway */ va_start(args, format); - len = vsnprintf(buffer, sizeof(buffer), format, args); - buffer[sizeof(buffer) - 1] = 0; + len = vsnprintf(request, sizeof(request), format, args); + request[sizeof(request) - 1] = 0; va_end(args); - if(len < 0 || (size_t)len > sizeof(buffer) - 1) { - logger(LOG_ERR, "Output buffer overflow while sending request to %s (%s)", + if(len < 0 || (size_t)len > sizeof(request) - 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Output buffer overflow while sending request to %s (%s)", c->name, c->hostname); return false; } - ifdebug(PROTOCOL) { - sscanf(buffer, "%d", &request); - ifdebug(META) - logger(LOG_DEBUG, "Sending %s to %s (%s): %s", - request_name[request], c->name, c->hostname, buffer); - else - logger(LOG_DEBUG, "Sending %s to %s (%s)", request_name[request], - c->name, c->hostname); - } + int id = atoi(request); + logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[id], c->name, c->hostname, request); - buffer[len++] = '\n'; + request[len++] = '\n'; if(c == everyone) { - broadcast_meta(NULL, buffer, len); + broadcast_meta(NULL, request, len); return true; } else { - return send_meta(c, buffer, len); - } -} - -void forward_request(connection_t *from) { - int request; - - ifdebug(PROTOCOL) { - sscanf(from->buffer, "%d", &request); - ifdebug(META) - logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s", - request_name[request], from->name, from->hostname, - from->buffer); - else - logger(LOG_DEBUG, "Forwarding %s from %s (%s)", - request_name[request], from->name, from->hostname); - } - - from->buffer[from->reqlen - 1] = '\n'; - - broadcast_meta(from, from->buffer, from->reqlen); -} - -bool receive_request(connection_t *c) { - int request; - - if(sscanf(c->buffer, "%d", &request) == 1) { - if((request < 0) || (request >= LAST) || !request_handlers[request]) { - ifdebug(META) - logger(LOG_DEBUG, "Unknown request from %s (%s): %s", - c->name, c->hostname, c->buffer); - else - logger(LOG_ERR, "Unknown request from %s (%s)", - c->name, c->hostname); - - return false; + if(id) { + return send_meta(c, request, len); } else { - ifdebug(PROTOCOL) { - ifdebug(META) - logger(LOG_DEBUG, "Got %s from %s (%s): %s", - request_name[request], c->name, c->hostname, - c->buffer); - else - logger(LOG_DEBUG, "Got %s from %s (%s)", - request_name[request], c->name, c->hostname); + send_meta_raw(c, request, len); + return true; + } + } +} + +void forward_request(connection_t *from, const char *request) { + logger(DEBUG_META, LOG_DEBUG, "Forwarding %s from %s (%s): %s", request_name[atoi(request)], from->name, from->hostname, request); + + // Create a temporary newline-terminated copy of the request + int len = strlen(request); + char tmp[len + 1]; + memcpy(tmp, request, len); + tmp[len] = '\n'; + broadcast_meta(from, tmp, sizeof(tmp)); +} + +bool receive_request(connection_t *c, const char *request) { + if(c->outgoing && proxytype == PROXY_HTTP && c->allow_request == ID) { + if(!request[0] || request[0] == '\r') { + return true; + } + + if(!strncasecmp(request, "HTTP/1.1 ", 9)) { + if(!strncmp(request + 9, "200", 3)) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + return true; + } else { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Proxy request rejected: %s", request + 9); + return false; } } + } - if((c->allow_request != ALL) && (c->allow_request != request)) { - logger(LOG_ERR, "Unauthorized request from %s (%s)", c->name, - c->hostname); + int reqno = atoi(request); + + if(reqno || *request == '0') { + if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) { + logger(DEBUG_META, LOG_DEBUG, "Unknown request from %s (%s): %s", c->name, c->hostname, request); + return false; + } else { + logger(DEBUG_META, LOG_DEBUG, "Got %s from %s (%s): %s", request_name[reqno], c->name, c->hostname, request); + } + + if((c->allow_request != ALL) && (c->allow_request != reqno)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unauthorized request from %s (%s)", c->name, c->hostname); return false; } - if(!request_handlers[request](c)) { + if(!request_handlers[reqno](c, request)) { /* Something went wrong. Probably scriptkiddies. Terminate. */ - logger(LOG_ERR, "Error while processing %s from %s (%s)", - request_name[request], c->name, c->hostname); + if(reqno != TERMREQ) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while processing %s from %s (%s)", request_name[reqno], c->name, c->hostname); + } + return false; } } else { - logger(LOG_ERR, "Bogus data received from %s (%s)", - c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus data received from %s (%s)", c->name, c->hostname); return false; } @@ -177,55 +165,60 @@ static int past_request_compare(const past_request_t *a, const past_request_t *b } static void free_past_request(past_request_t *r) { - if(r->request) { - free(r->request); - } - + free((char *)r->request); free(r); } -void init_requests(void) { - past_request_tree = avl_alloc_tree((avl_compare_t) past_request_compare, (avl_action_t) free_past_request); -} +static timeout_t past_request_timeout; -void exit_requests(void) { - avl_delete_tree(past_request_tree); -} - -bool seen_request(char *request) { - past_request_t *new, p = {0}; - - p.request = request; - - if(avl_search(past_request_tree, &p)) { - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Already seen request"); - return true; - } else { - new = xmalloc(sizeof(*new)); - new->request = xstrdup(request); - new->firstseen = now; - avl_insert(past_request_tree, new); - return false; - } -} - -void age_past_requests(void) { - avl_node_t *node, *next; - past_request_t *p; +static void age_past_requests(void *data) { + (void)data; int left = 0, deleted = 0; - for(node = past_request_tree->head; node; node = next) { - next = node->next; - p = node->data; - - if(p->firstseen + pinginterval <= now) { - avl_delete_node(past_request_tree, node), deleted++; + for splay_each(past_request_t, p, past_request_tree) { + if(p->firstseen + pinginterval <= now.tv_sec) { + splay_delete_node(past_request_tree, node), deleted++; } else { left++; } } - if(left || deleted) - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Aging past requests: deleted %d, left %d", - deleted, left); + if(left || deleted) { + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Aging past requests: deleted %d, left %d", deleted, left); + } + + if(left) + timeout_set(&past_request_timeout, &(struct timeval) { + 10, rand() % 100000 + }); +} + +bool seen_request(const char *request) { + past_request_t *new, p = {0}; + + p.request = request; + + if(splay_search(past_request_tree, &p)) { + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Already seen request"); + return true; + } else { + new = xmalloc(sizeof(*new)); + new->request = xstrdup(request); + new->firstseen = now.tv_sec; + splay_insert(past_request_tree, new); + timeout_add(&past_request_timeout, age_past_requests, NULL, &(struct timeval) { + 10, rand() % 100000 + }); + return false; + } +} + +void init_requests(void) { + past_request_tree = splay_alloc_tree((splay_compare_t) past_request_compare, (splay_action_t) free_past_request); +} + +void exit_requests(void) { + splay_delete_tree(past_request_tree); + + timeout_del(&past_request_timeout); } diff --git a/src/protocol.h b/src/protocol.h index a055f28..42981cc 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -4,7 +4,7 @@ /* protocol.h -- header for protocol.c Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2015 Guus Sliepen + 2000-2017 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 @@ -21,11 +21,12 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* Protocol version. Different versions are incompatible, - incompatible version have different protocols. - */ +#include "ecdsa.h" -#define PROT_CURRENT 17 +/* Protocol version. Different major versions are incompatible. */ + +#define PROT_MAJOR 17 +#define PROT_MINOR 7 /* Should not exceed 255! */ /* Silly Windows */ @@ -36,7 +37,6 @@ /* Request numbers */ typedef enum request_t { - PROXY = -2, ALL = -1, /* Guardian for allow_request */ ID = 0, METAKEY, CHALLENGE, CHAL_REPLY, ACK, STATUS, ERROR, TERMREQ, @@ -45,16 +45,25 @@ typedef enum request_t { ADD_EDGE, DEL_EDGE, KEY_CHANGED, REQ_KEY, ANS_KEY, PACKET, + /* Tinc 1.1 requests */ + CONTROL, + REQ_PUBKEY, ANS_PUBKEY, + SPTPS_PACKET, + UDP_INFO, MTU_INFO, LAST /* Guardian for the highest request number */ } request_t; typedef struct past_request_t { - char *request; + const char *request; time_t firstseen; } past_request_t; extern bool tunnelserver; extern bool strictsubnets; +extern bool experimental; + +extern int invitation_lifetime; +extern ecdsa_t *invitation_key; /* Maximum size of strings in a request. * scanf terminates %2048s with a NUL character, @@ -72,22 +81,22 @@ extern bool strictsubnets; /* Basic functions */ extern bool send_request(struct connection_t *c, const char *format, ...) __attribute__((__format__(printf, 2, 3))); -extern void forward_request(struct connection_t *c); -extern bool receive_request(struct connection_t *c); -extern bool check_id(const char *name); +extern void forward_request(struct connection_t *c, const char *request); +extern bool receive_request(struct connection_t *c, const char *request); extern void init_requests(void); extern void exit_requests(void); -extern bool seen_request(char *request); -extern void age_past_requests(void); +extern bool seen_request(const char *request); /* Requests */ extern bool send_id(struct connection_t *c); extern bool send_metakey(struct connection_t *c); +extern bool send_metakey_ec(struct connection_t *c); extern bool send_challenge(struct connection_t *c); extern bool send_chal_reply(struct connection_t *c); extern bool send_ack(struct connection_t *c); +extern bool send_termreq(struct connection_t *c); extern bool send_ping(struct connection_t *c); extern bool send_pong(struct connection_t *c); extern bool send_add_subnet(struct connection_t *c, const struct subnet_t *subnet); @@ -95,26 +104,36 @@ extern bool send_del_subnet(struct connection_t *c, const struct subnet_t *subne extern bool send_add_edge(struct connection_t *c, const struct edge_t *e); extern bool send_del_edge(struct connection_t *c, const struct edge_t *e); extern void send_key_changed(void); -extern bool send_req_key(struct node_t *n); -extern bool send_ans_key(struct node_t *n); +extern bool send_req_key(struct node_t *to); +extern bool send_ans_key(struct node_t *to); extern bool send_tcppacket(struct connection_t *c, const struct vpn_packet_t *packet); +extern bool send_sptps_tcppacket(struct connection_t *c, const char *packet, int len); +extern bool send_udp_info(struct node_t *from, struct node_t *to); +extern bool send_mtu_info(struct node_t *from, struct node_t *to, int mtu); /* Request handlers */ -extern bool id_h(struct connection_t *c); -extern bool metakey_h(struct connection_t *c); -extern bool challenge_h(struct connection_t *c); -extern bool chal_reply_h(struct connection_t *c); -extern bool ack_h(struct connection_t *c); -extern bool ping_h(struct connection_t *c); -extern bool pong_h(struct connection_t *c); -extern bool add_subnet_h(struct connection_t *c); -extern bool del_subnet_h(struct connection_t *c); -extern bool add_edge_h(struct connection_t *c); -extern bool del_edge_h(struct connection_t *c); -extern bool key_changed_h(struct connection_t *c); -extern bool req_key_h(struct connection_t *c); -extern bool ans_key_h(struct connection_t *c); -extern bool tcppacket_h(struct connection_t *c); +extern bool id_h(struct connection_t *c, const char *request); +extern bool metakey_h(struct connection_t *c, const char *request); +extern bool challenge_h(struct connection_t *c, const char *request); +extern bool chal_reply_h(struct connection_t *c, const char *request); +extern bool ack_h(struct connection_t *c, const char *request); +extern bool status_h(struct connection_t *c, const char *request); +extern bool error_h(struct connection_t *c, const char *request); +extern bool termreq_h(struct connection_t *c, const char *request); +extern bool ping_h(struct connection_t *c, const char *request); +extern bool pong_h(struct connection_t *c, const char *request); +extern bool add_subnet_h(struct connection_t *c, const char *request); +extern bool del_subnet_h(struct connection_t *c, const char *request); +extern bool add_edge_h(struct connection_t *c, const char *request); +extern bool del_edge_h(struct connection_t *c, const char *request); +extern bool key_changed_h(struct connection_t *c, const char *request); +extern bool req_key_h(struct connection_t *c, const char *request); +extern bool ans_key_h(struct connection_t *c, const char *request); +extern bool tcppacket_h(struct connection_t *c, const char *request); +extern bool sptps_tcppacket_h(struct connection_t *c, const char *request); +extern bool control_h(struct connection_t *c, const char *request); +extern bool udp_info_h(struct connection_t *c, const char *request); +extern bool mtu_info_h(struct connection_t *c, const char *request); #endif diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 15807c3..a270ffc 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -1,7 +1,7 @@ /* protocol_auth.c -- handle the meta-protocol, authentication Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2017 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 @@ -20,48 +20,384 @@ #include "system.h" -#include -#include -#include -#include - -#include "avl_tree.h" #include "conf.h" #include "connection.h" +#include "control.h" +#include "control_common.h" +#include "cipher.h" +#include "crypto.h" +#include "device.h" +#include "digest.h" +#include "ecdsa.h" #include "edge.h" #include "graph.h" #include "logger.h" #include "meta.h" +#include "names.h" #include "net.h" #include "netutl.h" #include "node.h" +#include "prf.h" #include "protocol.h" -#include "proxy.h" +#include "rsa.h" +#include "script.h" +#include "sptps.h" #include "utils.h" #include "xalloc.h" -bool send_id(connection_t *c) { - if(proxytype && c->outgoing && !c->status.proxy_passed) { - return send_proxyrequest(c); +#include "ed25519/sha512.h" + +int invitation_lifetime; +ecdsa_t *invitation_key = NULL; + +static bool send_proxyrequest(connection_t *c) { + switch(proxytype) { + case PROXY_HTTP: { + char *host; + char *port; + + sockaddr2str(&c->address, &host, &port); + send_request(c, "CONNECT %s:%s HTTP/1.1\r\n\r", host, port); + free(host); + free(port); + return true; } - return send_request(c, "%d %s %d", ID, myself->connection->name, - myself->connection->protocol_version); + case PROXY_SOCKS4: { + if(c->address.sa.sa_family != AF_INET) { + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!"); + return false; + } + + char s4req[9 + (proxyuser ? strlen(proxyuser) : 0)]; + s4req[0] = 4; + s4req[1] = 1; + memcpy(s4req + 2, &c->address.in.sin_port, 2); + memcpy(s4req + 4, &c->address.in.sin_addr, 4); + + if(proxyuser) { + memcpy(s4req + 8, proxyuser, strlen(proxyuser)); + } + + s4req[sizeof(s4req) - 1] = 0; + c->tcplen = 8; + return send_meta(c, s4req, sizeof(s4req)); + } + + case PROXY_SOCKS5: { + int len = 3 + 6 + (c->address.sa.sa_family == AF_INET ? 4 : 16); + c->tcplen = 2; + + if(proxypass) { + len += 3 + strlen(proxyuser) + strlen(proxypass); + } + + char s5req[len]; + int i = 0; + s5req[i++] = 5; + s5req[i++] = 1; + + if(proxypass) { + s5req[i++] = 2; + s5req[i++] = 1; + s5req[i++] = strlen(proxyuser); + memcpy(s5req + i, proxyuser, strlen(proxyuser)); + i += strlen(proxyuser); + s5req[i++] = strlen(proxypass); + memcpy(s5req + i, proxypass, strlen(proxypass)); + i += strlen(proxypass); + c->tcplen += 2; + } else { + s5req[i++] = 0; + } + + s5req[i++] = 5; + s5req[i++] = 1; + s5req[i++] = 0; + + if(c->address.sa.sa_family == AF_INET) { + s5req[i++] = 1; + memcpy(s5req + i, &c->address.in.sin_addr, 4); + i += 4; + memcpy(s5req + i, &c->address.in.sin_port, 2); + i += 2; + c->tcplen += 10; + } else if(c->address.sa.sa_family == AF_INET6) { + s5req[i++] = 3; + memcpy(s5req + i, &c->address.in6.sin6_addr, 16); + i += 16; + memcpy(s5req + i, &c->address.in6.sin6_port, 2); + i += 2; + c->tcplen += 22; + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Address family %x not supported for SOCKS 5 proxies!", c->address.sa.sa_family); + return false; + } + + if(i > len) { + abort(); + } + + return send_meta(c, s5req, sizeof(s5req)); + } + + case PROXY_SOCKS4A: + logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type not implemented yet"); + return false; + + case PROXY_EXEC: + return true; + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type"); + return false; + } } -bool id_h(connection_t *c) { +bool send_id(connection_t *c) { + gettimeofday(&c->start, NULL); + + int minor = 0; + + if(experimental) { + if(c->outgoing && !read_ecdsa_public_key(c)) { + minor = 1; + } else { + minor = myself->connection->protocol_minor; + } + } + + if(proxytype && c->outgoing) + if(!send_proxyrequest(c)) { + return false; + } + + return send_request(c, "%d %s %d.%d", ID, myself->connection->name, myself->connection->protocol_major, minor); +} + +static bool finalize_invitation(connection_t *c, const char *data, uint16_t len) { + (void)len; + + if(strchr(data, '\n')) { + logger(DEBUG_ALWAYS, LOG_ERR, "Received invalid key from invited node %s (%s)!\n", c->name, c->hostname); + return false; + } + + // Create a new host config file + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, c->name); + + if(!access(filename, F_OK)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Host config file for %s (%s) already exists!\n", c->name, c->hostname); + return false; + } + + FILE *f = fopen(filename, "w"); + + if(!f) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to create %s: %s\n", filename, strerror(errno)); + return false; + } + + fprintf(f, "Ed25519PublicKey = %s\n", data); + fclose(f); + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Key successfully received from %s (%s)", c->name, c->hostname); + + // Call invitation-accepted script + environment_t env; + char *address, *port; + + environment_init(&env); + environment_add(&env, "NODE=%s", c->name); + sockaddr2str(&c->address, &address, &port); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "NAME=%s", myself->name); + + execute_script("invitation-accepted", &env); + + environment_exit(&env); + + sptps_send_record(&c->sptps, 2, data, 0); + return true; +} + +static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) { + connection_t *c = handle; + + if(type == 128) { + return true; + } + + if(type == 1 && c->status.invitation_used) { + return finalize_invitation(c, data, len); + } + + if(type != 0 || len != 18 || c->status.invitation_used) { + return false; + } + + // Recover the filename from the cookie and the key + char *fingerprint = ecdsa_get_base64_public_key(invitation_key); + char hashbuf[18 + strlen(fingerprint)]; + char cookie[64]; + memcpy(hashbuf, data, 18); + memcpy(hashbuf + 18, fingerprint, sizeof(hashbuf) - 18); + sha512(hashbuf, sizeof(hashbuf), cookie); + b64encode_urlsafe(cookie, cookie, 18); + free(fingerprint); + + char filename[PATH_MAX], usedname[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "invitations" SLASH "%s", confbase, cookie); + snprintf(usedname, sizeof(usedname), "%s" SLASH "invitations" SLASH "%s.used", confbase, cookie); + + // Atomically rename the invitation file + if(rename(filename, usedname)) { + if(errno == ENOENT) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s tried to use non-existing invitation %s\n", c->hostname, cookie); + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to rename invitation %s\n", cookie); + } + + return false; + } + + // Check the timestamp of the invitation + struct stat st; + + if(stat(usedname, &st)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat %s", usedname); + return false; + } + + if(st.st_mtime + invitation_lifetime < now.tv_sec) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s tried to use expired invitation %s", c->hostname, cookie); + return false; + } + + // Open the renamed file + FILE *f = fopen(usedname, "r"); + + if(!f) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error trying to open invitation %s\n", cookie); + return false; + } + + // Read the new node's Name from the file + char buf[1024] = ""; + fgets(buf, sizeof(buf), f); + size_t buflen = strlen(buf); + + // Strip whitespace at the end + while(buflen && strchr(" \t\r\n", buf[buflen - 1])) { + buf[--buflen] = 0; + } + + // Split the first line into variable and value + len = strcspn(buf, " \t="); + char *name = buf + len; + name += strspn(name, " \t"); + + if(*name == '=') { + name++; + name += strspn(name, " \t"); + } + + buf[len] = 0; + + // Check that it is a valid Name + if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name) || !strcmp(name, myself->name)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid invitation file %s\n", cookie); + fclose(f); + return false; + } + + free(c->name); + c->name = xstrdup(name); + + // Send the node the contents of the invitation file + rewind(f); + size_t result; + + while((result = fread(buf, 1, sizeof(buf), f))) { + sptps_send_record(&c->sptps, 0, buf, result); + } + + sptps_send_record(&c->sptps, 1, buf, 0); + fclose(f); + unlink(usedname); + + c->status.invitation_used = true; + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Invitation %s successfully sent to %s (%s)", cookie, c->name, c->hostname); + return true; +} + +bool id_h(connection_t *c, const char *request) { char name[MAX_STRING_SIZE]; - if(sscanf(c->buffer, "%*d " MAX_STRING " %d", name, &c->protocol_version) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name, + if(sscanf(request, "%*d " MAX_STRING " %2d.%3d", name, &c->protocol_major, &c->protocol_minor) < 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name, c->hostname); return false; } + /* Check if this is a control connection */ + + if(name[0] == '^' && !strcmp(name + 1, controlcookie)) { + c->status.control = true; + c->allow_request = CONTROL; + c->last_ping_time = now.tv_sec + 3600; + + free(c->name); + c->name = xstrdup(""); + + if(!c->outgoing) { + send_id(c); + } + + return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid()); + } + + if(name[0] == '?') { + if(!invitation_key) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got invitation from %s but we don't have an invitation key", c->hostname); + return false; + } + + c->ecdsa = ecdsa_set_base64_public_key(name + 1); + + if(!c->ecdsa) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad invitation from %s", c->hostname); + return false; + } + + c->status.invitation = true; + char *mykey = ecdsa_get_base64_public_key(invitation_key); + + if(!mykey) { + return false; + } + + if(!c->outgoing) { + send_id(c); + } + + if(!send_request(c, "%d %s", ACK, mykey)) { + return false; + } + + free(mykey); + + c->protocol_minor = 2; + + return sptps_start(&c->sptps, c, false, false, invitation_key, c->ecdsa, "tinc invitation", 15, send_meta_sptps, receive_invitation_sptps); + } + /* Check if identity is a valid name */ if(!check_id(name) || !strcmp(name, myself->name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name, c->hostname, "invalid name"); return false; } @@ -70,23 +406,20 @@ bool id_h(connection_t *c) { if(c->outgoing) { if(strcmp(c->name, name)) { - logger(LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name, + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name, c->name); return false; } } else { - if(c->name) { - free(c->name); - } - + free(c->name); c->name = xstrdup(name); } /* Check if version matches */ - if(c->protocol_version != myself->connection->protocol_version) { - logger(LOG_ERR, "Peer %s (%s) uses incompatible version %d", - c->name, c->hostname, c->protocol_version); + if(c->protocol_major != myself->connection->protocol_major) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d", + c->name, c->hostname, c->protocol_major, c->protocol_minor); return false; } @@ -104,17 +437,34 @@ bool id_h(connection_t *c) { return send_ack(c); } + if(!experimental) { + c->protocol_minor = 0; + } + if(!c->config_tree) { init_configuration(&c->config_tree); - if(!read_connection_config(c)) { - logger(LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, - c->name); + if(!read_host_config(c->config_tree, c->name, false)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, c->name); return false; } + + if(experimental) { + read_ecdsa_public_key(c); + } + + /* Ignore failures if no key known yet */ } - if(!read_rsa_public_key(c)) { + if(c->protocol_minor && !ecdsa_active(c->ecdsa)) { + c->protocol_minor = 1; + } + + /* Forbid version rollback for nodes whose Ed25519 key we know */ + + if(ecdsa_active(c->ecdsa) && c->protocol_minor < 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) tries to roll back protocol version to %d.%d", + c->name, c->hostname, c->protocol_major, c->protocol_minor); return false; } @@ -124,51 +474,66 @@ bool id_h(connection_t *c) { send_id(c); } - return send_metakey(c); -} + if(c->protocol_minor >= 2) { + c->allow_request = ACK; + char label[25 + strlen(myself->name) + strlen(c->name)]; -static uint64_t byte_budget(const EVP_CIPHER *cipher) { - /* Hopefully some failsafe way to calculate the maximum amount of bytes to - send/receive with a given cipher before we might run into birthday paradox - attacks. Because we might use different modes, the block size of the mode - might be 1 byte. In that case, use the IV length. Ensure the whole thing - is limited to what can be represented with a 64 bits integer. - */ - - int ivlen = EVP_CIPHER_iv_length(cipher); - int blklen = EVP_CIPHER_block_size(cipher); - int len = blklen > 1 ? blklen : ivlen > 1 ? ivlen : 8; - int bits = len * 4 - 1; - return bits < 64 ? UINT64_C(1) << bits : UINT64_MAX; -} - -bool send_metakey(connection_t *c) { - bool x; - - int len = RSA_size(c->rsa_key); - - /* Allocate buffers for the meta key */ - - char buffer[2 * len + 1]; - - c->outkey = xrealloc(c->outkey, len); - - if(!c->outctx) { - c->outctx = EVP_CIPHER_CTX_new(); - - if(!c->outctx) { - abort(); + if(c->outgoing) { + snprintf(label, sizeof(label), "tinc TCP key expansion %s %s", myself->name, c->name); + } else { + snprintf(label, sizeof(label), "tinc TCP key expansion %s %s", c->name, myself->name); } + + return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, sizeof(label), send_meta_sptps, receive_meta_sptps); + } else { + return send_metakey(c); } +} - /* Copy random data to the buffer */ - - if(1 != RAND_bytes((unsigned char *)c->outkey, len)) { - int err = ERR_get_error(); - logger(LOG_ERR, "Failed to generate meta key (%s)", ERR_error_string(err, NULL)); +#ifndef DISABLE_LEGACY +bool send_metakey(connection_t *c) { + if(!myself->connection->rsa) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Peer %s (%s) uses legacy protocol which we don't support", c->name, c->hostname); return false; } + if(!read_rsa_public_key(c)) { + return false; + } + + /* We need to use a stream mode for the meta protocol. Use AES for this, + but try to match the key size with the one from the cipher selected + by Cipher. + */ + + int keylen = cipher_keylength(myself->incipher); + + if(keylen <= 16) { + c->outcipher = cipher_open_by_name("aes-128-cfb"); + } else if(keylen <= 24) { + c->outcipher = cipher_open_by_name("aes-192-cfb"); + } else { + c->outcipher = cipher_open_by_name("aes-256-cfb"); + } + + if(!c) { + return false; + } + + c->outbudget = cipher_budget(c->outcipher); + + if(!(c->outdigest = digest_open_by_name("sha256", -1))) { + return false; + } + + const size_t len = rsa_size(c->rsa); + char key[len]; + char enckey[len]; + char hexkey[2 * len + 1]; + + /* Create a random key */ + + randomize(key, len); /* The message we send must be smaller than the modulus of the RSA key. By definition, for a key of k bits, the following formula holds: @@ -180,13 +545,15 @@ bool send_metakey(connection_t *c) { This can be done by setting the most significant bit to zero. */ - c->outkey[0] &= 0x7F; + key[0] &= 0x7F; - ifdebug(SCARY_THINGS) { - bin2hex(c->outkey, buffer, len); - buffer[len * 2] = '\0'; - logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s", - buffer); + if(!cipher_set_key_from_rsa(c->outcipher, key, len, true)) { + return false; + } + + if(debug_level >= DEBUG_SCARY_THINGS) { + bin2hex(key, hexkey, len); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Generated random meta key (unencrypted): %s", hexkey); } /* Encrypt the random data @@ -196,144 +563,90 @@ bool send_metakey(connection_t *c) { with a length equal to that of the modulus of the RSA key. */ - if(RSA_public_encrypt(len, (unsigned char *)c->outkey, (unsigned char *)buffer, c->rsa_key, RSA_NO_PADDING) != len) { - logger(LOG_ERR, "Error during encryption of meta key for %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!rsa_public_encrypt(c->rsa, key, len, enckey)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during encryption of meta key for %s (%s)", c->name, c->hostname); return false; } /* Convert the encrypted random data to a hexadecimal formatted string */ - bin2hex(buffer, buffer, len); - buffer[len * 2] = '\0'; + bin2hex(enckey, hexkey, len); /* Send the meta key */ - x = send_request(c, "%d %d %d %d %d %s", METAKEY, - c->outcipher ? EVP_CIPHER_nid(c->outcipher) : 0, - c->outdigest ? EVP_MD_type(c->outdigest) : 0, c->outmaclength, - c->outcompression, buffer); + bool result = send_request(c, "%d %d %d %d %d %s", METAKEY, + cipher_get_nid(c->outcipher), + digest_get_nid(c->outdigest), c->outmaclength, + c->outcompression, hexkey); - /* Further outgoing requests are encrypted with the key we just generated */ - - if(c->outcipher) { - if(!EVP_EncryptInit(c->outctx, c->outcipher, - (unsigned char *)c->outkey + len - EVP_CIPHER_key_length(c->outcipher), - (unsigned char *)c->outkey + len - EVP_CIPHER_key_length(c->outcipher) - - EVP_CIPHER_iv_length(c->outcipher))) { - logger(LOG_ERR, "Error during initialisation of cipher for %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); - return false; - } - - c->outbudget = byte_budget(c->outcipher); - c->status.encryptout = true; - } - - return x; + c->status.encryptout = true; + return result; } -bool metakey_h(connection_t *c) { - char buffer[MAX_STRING_SIZE]; +bool metakey_h(connection_t *c, const char *request) { + if(!myself->connection->rsa) { + return false; + } + + char hexkey[MAX_STRING_SIZE]; int cipher, digest, maclength, compression; - int len; + const size_t len = rsa_size(myself->connection->rsa); + char enckey[len]; + char key[len]; - if(sscanf(c->buffer, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, buffer) != 5) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, - c->hostname); + if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname); return false; } - len = RSA_size(myself->connection->rsa_key); - - /* Check if the length of the meta key is all right */ - - if(strlen(buffer) != (size_t)len * 2) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength"); - return false; - } - - /* Allocate buffers for the meta key */ - - c->inkey = xrealloc(c->inkey, len); - - if(!c->inctx) { - c->inctx = EVP_CIPHER_CTX_new(); - - if(!c->inctx) { - abort(); - } - } - /* Convert the challenge from hexadecimal back to binary */ - if(!hex2bin(buffer, buffer, len)) { - logger(LOG_ERR, "Got bad %s from %s(%s): %s", "METAKEY", c->name, c->hostname, "invalid key"); + size_t inlen = hex2bin(hexkey, enckey, sizeof(enckey)); + + /* Check if the length of the meta key is all right */ + + if(inlen != len) { + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength"); return false; } /* Decrypt the meta key */ - if(RSA_private_decrypt(len, (unsigned char *)buffer, (unsigned char *)c->inkey, myself->connection->rsa_key, RSA_NO_PADDING) != len) { /* See challenge() */ - logger(LOG_ERR, "Error during decryption of meta key for %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!rsa_private_decrypt(myself->connection->rsa, enckey, len, key)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname); return false; } - ifdebug(SCARY_THINGS) { - bin2hex(c->inkey, buffer, len); - buffer[len * 2] = '\0'; - logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", buffer); + if(debug_level >= DEBUG_SCARY_THINGS) { + bin2hex(key, hexkey, len); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey); } - /* All incoming requests will now be encrypted. */ - /* Check and lookup cipher and digest algorithms */ if(cipher) { - c->incipher = EVP_get_cipherbynid(cipher); - - if(!c->incipher) { - logger(LOG_ERR, "%s (%s) uses unknown cipher!", c->name, c->hostname); + if(!(c->incipher = cipher_open_by_nid(cipher)) || !cipher_set_key_from_rsa(c->incipher, key, len, false)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname); return false; } - - if(!EVP_DecryptInit(c->inctx, c->incipher, - (unsigned char *)c->inkey + len - EVP_CIPHER_key_length(c->incipher), - (unsigned char *)c->inkey + len - EVP_CIPHER_key_length(c->incipher) - - EVP_CIPHER_iv_length(c->incipher))) { - logger(LOG_ERR, "Error during initialisation of cipher from %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); - return false; - } - - c->inbudget = byte_budget(c->incipher); - c->status.decryptin = true; } else { - logger(LOG_ERR, "%s (%s) uses null cipher!", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null cipher"); return false; } - c->inmaclength = maclength; + c->inbudget = cipher_budget(c->incipher); if(digest) { - c->indigest = EVP_get_digestbynid(digest); - - if(!c->indigest) { - logger(LOG_ERR, "Node %s (%s) uses unknown digest!", c->name, c->hostname); - return false; - } - - if(c->inmaclength > EVP_MD_size(c->indigest) || c->inmaclength < 0) { - logger(LOG_ERR, "%s (%s) uses bogus MAC length!", c->name, c->hostname); + if(!(c->indigest = digest_open_by_nid(digest, -1))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of digest from %s (%s)", c->name, c->hostname); return false; } } else { - logger(LOG_ERR, "%s (%s) uses null digest!", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null digest"); return false; } - c->incompression = compression; + c->status.decryptin = true; c->allow_request = CHALLENGE; @@ -341,69 +654,54 @@ bool metakey_h(connection_t *c) { } bool send_challenge(connection_t *c) { - /* CHECKME: what is most reasonable value for len? */ - - int len = RSA_size(c->rsa_key); - - /* Allocate buffers for the challenge */ - - char buffer[2 * len + 1]; + const size_t len = rsa_size(c->rsa); + char buffer[len * 2 + 1]; c->hischallenge = xrealloc(c->hischallenge, len); /* Copy random data to the buffer */ - if(1 != RAND_bytes((unsigned char *)c->hischallenge, len)) { - int err = ERR_get_error(); - logger(LOG_ERR, "Failed to generate challenge (%s)", ERR_error_string(err, NULL)); - return false; // Do not send predictable challenges, let connection attempt fail. - } + randomize(c->hischallenge, len); /* Convert to hex */ bin2hex(c->hischallenge, buffer, len); - buffer[len * 2] = '\0'; /* Send the challenge */ return send_request(c, "%d %s", CHALLENGE, buffer); } -bool challenge_h(connection_t *c) { - char buffer[MAX_STRING_SIZE]; - int len; - - if(sscanf(c->buffer, "%*d " MAX_STRING, buffer) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, - c->hostname); +bool challenge_h(connection_t *c, const char *request) { + if(!myself->connection->rsa) { return false; } - len = RSA_size(myself->connection->rsa_key); + char buffer[MAX_STRING_SIZE]; + const size_t len = rsa_size(myself->connection->rsa); + + if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname); + return false; + } /* Check if the length of the challenge is all right */ if(strlen(buffer) != (size_t)len * 2) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, - c->hostname, "wrong challenge length"); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge length"); return false; } - /* Allocate buffers for the challenge */ - c->mychallenge = xrealloc(c->mychallenge, len); /* Convert the challenge from hexadecimal back to binary */ - if(!hex2bin(buffer, c->mychallenge, len)) { - logger(LOG_ERR, "Got bad %s from %s(%s): %s", "CHALLENGE", c->name, c->hostname, "invalid challenge"); - return false; - } + hex2bin(buffer, c->mychallenge, len); + + /* The rest is done by send_chal_reply() */ c->allow_request = CHAL_REPLY; - /* Rest is done by send_chal_reply() */ - if(c->outgoing) { return send_chal_reply(c); } else { @@ -412,95 +710,53 @@ bool challenge_h(connection_t *c) { } bool send_chal_reply(connection_t *c) { - char hash[EVP_MAX_MD_SIZE * 2 + 1]; - EVP_MD_CTX *ctx; + const size_t len = rsa_size(myself->connection->rsa); + size_t digestlen = digest_length(c->indigest); + char digest[digestlen * 2 + 1]; /* Calculate the hash from the challenge we received */ - ctx = EVP_MD_CTX_create(); - - if(!ctx) { - abort(); - } - - if(!EVP_DigestInit(ctx, c->indigest) - || !EVP_DigestUpdate(ctx, c->mychallenge, RSA_size(myself->connection->rsa_key)) - || !EVP_DigestFinal(ctx, (unsigned char *)hash, NULL)) { - EVP_MD_CTX_destroy(ctx); - logger(LOG_ERR, "Error during calculation of response for %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); + if(!digest_create(c->indigest, c->mychallenge, len, digest)) { return false; } - EVP_MD_CTX_destroy(ctx); + free(c->mychallenge); + c->mychallenge = NULL; /* Convert the hash to a hexadecimal formatted string */ - bin2hex(hash, hash, EVP_MD_size(c->indigest)); - hash[EVP_MD_size(c->indigest) * 2] = '\0'; + bin2hex(digest, digest, digestlen); /* Send the reply */ - return send_request(c, "%d %s", CHAL_REPLY, hash); + return send_request(c, "%d %s", CHAL_REPLY, digest); } -bool chal_reply_h(connection_t *c) { +bool chal_reply_h(connection_t *c, const char *request) { char hishash[MAX_STRING_SIZE]; - char myhash[EVP_MAX_MD_SIZE]; - EVP_MD_CTX *ctx; - if(sscanf(c->buffer, "%*d " MAX_STRING, hishash) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name, + if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name, c->hostname); return false; } - /* Check if the length of the hash is all right */ - - if(strlen(hishash) != (size_t)EVP_MD_size(c->outdigest) * 2) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, - c->hostname, "wrong challenge reply length"); - return false; - } - /* Convert the hash to binary format */ - if(!hex2bin(hishash, hishash, EVP_MD_size(c->outdigest))) { - logger(LOG_ERR, "Got bad %s from %s(%s): %s", "CHAL_REPLY", c->name, c->hostname, "invalid hash"); + size_t inlen = hex2bin(hishash, hishash, sizeof(hishash)); + + /* Check if the length of the hash is all right */ + + if(inlen != digest_length(c->outdigest)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length"); return false; } - /* Calculate the hash from the challenge we sent */ - ctx = EVP_MD_CTX_create(); - - if(!ctx) { - abort(); - } - - if(!EVP_DigestInit(ctx, c->outdigest) - || !EVP_DigestUpdate(ctx, c->hischallenge, RSA_size(c->rsa_key)) - || !EVP_DigestFinal(ctx, (unsigned char *)myhash, NULL)) { - EVP_MD_CTX_destroy(ctx); - logger(LOG_ERR, "Error during calculation of response from %s (%s): %s", - c->name, c->hostname, ERR_error_string(ERR_get_error(), NULL)); - return false; - } - - EVP_MD_CTX_destroy(ctx); - - /* Verify the incoming hash with the calculated hash */ - - if(memcmp(hishash, myhash, EVP_MD_size(c->outdigest))) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, - c->hostname, "wrong challenge reply"); - - ifdebug(SCARY_THINGS) { - bin2hex(myhash, hishash, SHA_DIGEST_LENGTH); - hishash[SHA_DIGEST_LENGTH * 2] = '\0'; - logger(LOG_DEBUG, "Expected challenge reply: %s", hishash); - } + /* Verify the hash */ + if(!digest_verify(c->outdigest, c->hischallenge, rsa_size(c->rsa), hishash)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply"); return false; } @@ -508,6 +764,8 @@ bool chal_reply_h(connection_t *c) { Send an acknowledgement with the rest of the information needed. */ + free(c->hischallenge); + c->hischallenge = NULL; c->allow_request = ACK; if(!c->outgoing) { @@ -517,7 +775,65 @@ bool chal_reply_h(connection_t *c) { return send_ack(c); } +static bool send_upgrade(connection_t *c) { + /* Special case when protocol_minor is 1: the other end is Ed25519 capable, + * but doesn't know our key yet. So send it now. */ + + char *pubkey = ecdsa_get_base64_public_key(myself->connection->ecdsa); + + if(!pubkey) { + return false; + } + + bool result = send_request(c, "%d %s", ACK, pubkey); + free(pubkey); + return result; +} +#else +bool send_metakey(connection_t *c) { + (void)c; + return false; +} + +bool metakey_h(connection_t *c, const char *request) { + (void)c; + (void)request; + return false; +} + +bool send_challenge(connection_t *c) { + (void)c; + return false; +} + +bool challenge_h(connection_t *c, const char *request) { + (void)c; + (void)request; + return false; +} + +bool send_chal_reply(connection_t *c) { + (void)c; + return false; +} + +bool chal_reply_h(connection_t *c, const char *request) { + (void)c; + (void)request; + return false; +} + +static bool send_upgrade(connection_t *c) { + (void)c; + return false; +} +#endif + bool send_ack(connection_t *c) { + if(c->protocol_minor == 1) { + return send_upgrade(c); + } + /* ACK message contains rest of the information the other end needs to create node_t and edge_t structures. */ @@ -550,52 +866,100 @@ bool send_ack(connection_t *c) { c->options |= OPTION_CLAMP_MSS; } - get_config_int(lookup_config(c->config_tree, "Weight"), &c->estimated_weight); + if(!get_config_int(lookup_config(c->config_tree, "Weight"), &c->estimated_weight)) { + get_config_int(lookup_config(config_tree, "Weight"), &c->estimated_weight); + } - return send_request(c, "%d %s %d %x", ACK, myport, c->estimated_weight, c->options); + return send_request(c, "%d %s %d %x", ACK, myport, c->estimated_weight, (c->options & 0xffffff) | (experimental ? (PROT_MINOR << 24) : 0)); } static void send_everything(connection_t *c) { - avl_node_t *node, *node2; - node_t *n; - subnet_t *s; - edge_t *e; - /* Send all known subnets and edges */ + if(disablebuggypeers) { + static struct { + vpn_packet_t pkt; + char pad[MAXBUFSIZE - MAXSIZE]; + } zeropkt; + + memset(&zeropkt, 0, sizeof(zeropkt)); + zeropkt.pkt.len = MAXBUFSIZE; + send_tcppacket(c, &zeropkt.pkt); + } + if(tunnelserver) { - for(node = myself->subnet_tree->head; node; node = node->next) { - s = node->data; + for splay_each(subnet_t, s, myself->subnet_tree) { send_add_subnet(c, s); } return; } - for(node = node_tree->head; node; node = node->next) { - n = node->data; - - for(node2 = n->subnet_tree->head; node2; node2 = node2->next) { - s = node2->data; + for splay_each(node_t, n, node_tree) { + for splay_each(subnet_t, s, n->subnet_tree) { send_add_subnet(c, s); } - for(node2 = n->edge_tree->head; node2; node2 = node2->next) { - e = node2->data; + for splay_each(edge_t, e, n->edge_tree) { send_add_edge(c, e); } } } -bool ack_h(connection_t *c) { +static bool upgrade_h(connection_t *c, const char *request) { + char pubkey[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d " MAX_STRING, pubkey) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); + return false; + } + + if(ecdsa_active(c->ecdsa) || read_ecdsa_public_key(c)) { + char *knownkey = ecdsa_get_base64_public_key(c->ecdsa); + bool different = strcmp(knownkey, pubkey); + free(knownkey); + + if(different) { + logger(DEBUG_ALWAYS, LOG_ERR, "Already have an Ed25519 public key from %s (%s) which is different from the one presented now!", c->name, c->hostname); + return false; + } + + logger(DEBUG_ALWAYS, LOG_INFO, "Already have Ed25519 public key from %s (%s), ignoring.", c->name, c->hostname); + c->allow_request = TERMREQ; + return send_termreq(c); + } + + c->ecdsa = ecdsa_set_base64_public_key(pubkey); + + if(!c->ecdsa) { + logger(DEBUG_ALWAYS, LOG_INFO, "Got bad Ed25519 public key from %s (%s), not upgrading.", c->name, c->hostname); + return false; + } + + logger(DEBUG_ALWAYS, LOG_INFO, "Got Ed25519 public key from %s (%s), upgrading!", c->name, c->hostname); + append_config_file(c->name, "Ed25519PublicKey", pubkey); + c->allow_request = TERMREQ; + + if(c->outgoing) { + c->outgoing->timeout = 0; + } + + return send_termreq(c); +} + +bool ack_h(connection_t *c, const char *request) { + if(c->protocol_minor == 1) { + return upgrade_h(c, request); + } + char hisport[MAX_STRING_SIZE]; int weight, mtu; uint32_t options; node_t *n; bool choice; - if(sscanf(c->buffer, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, + if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); return false; } @@ -611,8 +975,18 @@ bool ack_h(connection_t *c) { } else { if(n->connection) { /* Oh dear, we already have a connection to this node. */ - ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", - n->name, n->hostname); + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname); + + if(n->connection->outgoing) { + if(c->outgoing) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Two outgoing connections to the same node!"); + } else { + c->outgoing = n->connection->outgoing; + } + + n->connection->outgoing = NULL; + } + terminate_connection(n->connection, false); /* Run graph algorithm to purge key and make sure up/down scripts are rerun with new IP addresses and stuff */ graph(); @@ -648,10 +1022,9 @@ bool ack_h(connection_t *c) { /* Activate this connection */ c->allow_request = ALL; - c->status.active = true; - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection with %s (%s) activated", c->name, - c->hostname); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection with %s (%s) activated", c->name, + c->hostname); /* Send him everything we know */ @@ -664,6 +1037,16 @@ bool ack_h(connection_t *c) { c->edge->to = n; sockaddrcpy(&c->edge->address, &c->address); sockaddr_setport(&c->edge->address, hisport); + sockaddr_t local_sa; + socklen_t local_salen = sizeof(local_sa); + + if(getsockname(c->socket, &local_sa.sa, &local_salen) < 0) { + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not get local socket address for connection with %s", c->name); + } else { + sockaddr_setport(&local_sa, myport); + c->edge->local_address = local_sa; + } + c->edge->weight = (weight + c->estimated_weight) / 2; c->edge->connection = c; c->edge->options = c->options; diff --git a/src/protocol_edge.c b/src/protocol_edge.c index a1cf640..d650c36 100644 --- a/src/protocol_edge.c +++ b/src/protocol_edge.c @@ -1,7 +1,7 @@ /* protocol_edge.c -- handle the meta-protocol, edges Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2012 Guus Sliepen 2009 Michael Tokarev This program is free software; you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "system.h" -#include "avl_tree.h" #include "conf.h" #include "connection.h" #include "edge.h" @@ -41,29 +40,45 @@ bool send_add_edge(connection_t *c, const edge_t *e) { sockaddr2str(&e->address, &address, &port); - x = send_request(c, "%d %x %s %s %s %s %x %d", ADD_EDGE, rand(), - e->from->name, e->to->name, address, port, - e->options, e->weight); + if(e->local_address.sa.sa_family) { + char *local_address, *local_port; + sockaddr2str(&e->local_address, &local_address, &local_port); + + x = send_request(c, "%d %x %s %s %s %s %x %d %s %s", ADD_EDGE, rand(), + e->from->name, e->to->name, address, port, + e->options, e->weight, local_address, local_port); + free(local_address); + free(local_port); + } else { + x = send_request(c, "%d %x %s %s %s %s %x %d", ADD_EDGE, rand(), + e->from->name, e->to->name, address, port, + e->options, e->weight); + } + free(address); free(port); return x; } -bool add_edge_h(connection_t *c) { +bool add_edge_h(connection_t *c, const char *request) { edge_t *e; node_t *from, *to; char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; char to_address[MAX_STRING_SIZE]; char to_port[MAX_STRING_SIZE]; - sockaddr_t address; + char address_local[MAX_STRING_SIZE]; + char port_local[MAX_STRING_SIZE]; + sockaddr_t address, local_address = {0}; uint32_t options; int weight; - if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d", - from_name, to_name, to_address, to_port, &options, &weight) != 6) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, + int parameter_count = sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d "MAX_STRING" "MAX_STRING, + from_name, to_name, to_address, to_port, &options, &weight, address_local, port_local); + + if(parameter_count != 6 && parameter_count != 8) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, c->hostname); return false; } @@ -71,12 +86,12 @@ bool add_edge_h(connection_t *c) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name) || !strcmp(from_name, to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, c->hostname, "invalid name"); return false; } - if(seen_request(c->buffer)) { + if(seen_request(request)) { return true; } @@ -89,9 +104,9 @@ bool add_edge_h(connection_t *c) { from != myself && from != c->node && to != myself && to != c->node) { /* ignore indirect edge registrations for tunnelserver */ - ifdebug(PROTOCOL) logger(LOG_WARNING, - "Ignoring indirect %s from %s (%s)", - "ADD_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, + "Ignoring indirect %s from %s (%s)", + "ADD_EDGE", c->name, c->hostname); return true; } @@ -112,63 +127,88 @@ bool add_edge_h(connection_t *c) { address = str2sockaddr(to_address, to_port); + if(parameter_count >= 8) { + local_address = str2sockaddr(address_local, port_local); + } + /* Check if edge already exists */ e = lookup_edge(from, to); if(e) { - if(e->weight != weight || e->options != options || sockaddrcmp(&e->address, &address)) { - if(from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", - "ADD_EDGE", c->name, c->hostname); - send_add_edge(c, e); - return true; - } else { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not match existing entry", - "ADD_EDGE", c->name, c->hostname); - e->options = options; + bool new_address = sockaddrcmp(&e->address, &address); + // local_address.sa.sa_family will be 0 if we got it from older tinc versions + // local_address.sa.sa_family will be 255 (AF_UNKNOWN) if we got it from newer versions + // but for edge which does not have local_address + bool new_local_address = local_address.sa.sa_family && local_address.sa.sa_family != AF_UNKNOWN && + sockaddrcmp(&e->local_address, &local_address); - if(sockaddrcmp(&e->address, &address)) { - sockaddrfree(&e->address); - e->address = address; - } - - if(e->weight != weight) { - avl_node_t *node = avl_unlink(edge_weight_tree, e); - e->weight = weight; - avl_insert_node(edge_weight_tree, node); - } - - goto done; - } - } else { + if(e->weight == weight && e->options == options && !new_address && !new_local_address) { + sockaddrfree(&address); + sockaddrfree(&local_address); return true; } + + if(from == myself) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", + "ADD_EDGE", c->name, c->hostname); + send_add_edge(c, e); + sockaddrfree(&address); + sockaddrfree(&local_address); + return true; + } + + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not match existing entry", + "ADD_EDGE", c->name, c->hostname); + + e->options = options; + + if(new_address) { + sockaddrfree(&e->address); + e->address = address; + } else { + sockaddrfree(&address); + } + + if(new_local_address) { + sockaddrfree(&e->local_address); + e->local_address = local_address; + } else { + sockaddrfree(&local_address); + } + + if(e->weight != weight) { + splay_node_t *node = splay_unlink(edge_weight_tree, e); + e->weight = weight; + splay_insert_node(edge_weight_tree, node); + } } else if(from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist", - "ADD_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist", + "ADD_EDGE", c->name, c->hostname); contradicting_add_edge++; e = new_edge(); e->from = from; e->to = to; send_del_edge(c, e); free_edge(e); + sockaddrfree(&address); + sockaddrfree(&local_address); return true; + } else { + e = new_edge(); + e->from = from; + e->to = to; + e->address = address; + e->local_address = local_address; + e->options = options; + e->weight = weight; + edge_add(e); } - e = new_edge(); - e->from = from; - e->to = to; - e->address = address; - e->options = options; - e->weight = weight; - edge_add(e); - -done: /* Tell the rest about the new edge */ if(!tunnelserver) { - forward_request(c); + forward_request(c, request); } /* Run MST before or after we tell the rest? */ @@ -183,14 +223,14 @@ bool send_del_edge(connection_t *c, const edge_t *e) { e->from->name, e->to->name); } -bool del_edge_h(connection_t *c) { +bool del_edge_h(connection_t *c, const char *request) { edge_t *e; char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; node_t *from, *to; - if(sscanf(c->buffer, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, + if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, c->hostname); return false; } @@ -198,12 +238,12 @@ bool del_edge_h(connection_t *c) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name) || !strcmp(from_name, to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, c->hostname, "invalid name"); return false; } - if(seen_request(c->buffer)) { + if(seen_request(request)) { return true; } @@ -216,21 +256,21 @@ bool del_edge_h(connection_t *c) { from != myself && from != c->node && to != myself && to != c->node) { /* ignore indirect edge registrations for tunnelserver */ - ifdebug(PROTOCOL) logger(LOG_WARNING, - "Ignoring indirect %s from %s (%s)", - "DEL_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, + "Ignoring indirect %s from %s (%s)", + "DEL_EDGE", c->name, c->hostname); return true; } if(!from) { - ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", - "DEL_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + "DEL_EDGE", c->name, c->hostname); return true; } if(!to) { - ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", - "DEL_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + "DEL_EDGE", c->name, c->hostname); return true; } @@ -239,14 +279,14 @@ bool del_edge_h(connection_t *c) { e = lookup_edge(from, to); if(!e) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", - "DEL_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", + "DEL_EDGE", c->name, c->hostname); return true; } if(e->from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", - "DEL_EDGE", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", + "DEL_EDGE", c->name, c->hostname); contradicting_del_edge++; send_add_edge(c, e); /* Send back a correction */ return true; @@ -255,7 +295,7 @@ bool del_edge_h(connection_t *c) { /* Tell the rest about the deleted edge */ if(!tunnelserver) { - forward_request(c); + forward_request(c, request); } /* Delete the edge */ diff --git a/src/protocol_key.c b/src/protocol_key.c index 6140a53..d9c58d9 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -1,7 +1,7 @@ /* protocol_key.c -- handle the meta-protocol, key exchange Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2016 Guus Sliepen + 2000-2017 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 @@ -20,102 +20,266 @@ #include "system.h" -#include -#include -#include - -#include "avl_tree.h" +#include "cipher.h" #include "connection.h" +#include "crypto.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "node.h" +#include "prf.h" #include "protocol.h" +#include "route.h" +#include "sptps.h" #include "utils.h" #include "xalloc.h" +#ifndef DISABLE_LEGACY static bool mykeyused = false; +#endif void send_key_changed(void) { - avl_node_t *node; - connection_t *c; - +#ifndef DISABLE_LEGACY send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name); /* Immediately send new keys to directly connected nodes to keep UDP mappings alive */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - - if(c->status.active && c->node && c->node->status.reachable) { + for list_each(connection_t, c, connection_list) + if(c->edge && c->node && c->node->status.reachable && !c->node->status.sptps) { send_ans_key(c->node); } + +#endif + + /* Force key exchange for connections using SPTPS */ + + if(experimental) { + for splay_each(node_t, n, node_tree) + if(n->status.reachable && n->status.validkey && n->status.sptps) { + sptps_force_kex(&n->sptps); + } } } -bool key_changed_h(connection_t *c) { +bool key_changed_h(connection_t *c, const char *request) { char name[MAX_STRING_SIZE]; node_t *n; - if(sscanf(c->buffer, "%*d %*x " MAX_STRING, name) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED", + if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED", c->name, c->hostname); return false; } - if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "KEY_CHANGED", c->name, c->hostname, "invalid name"); - return false; - } - - if(seen_request(c->buffer)) { + if(seen_request(request)) { return true; } n = lookup_node(name); if(!n) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist", "KEY_CHANGED", c->name, c->hostname, name); return true; } - n->status.validkey = false; - n->last_req_key = 0; + if(!n->status.sptps) { + n->status.validkey = false; + n->last_req_key = 0; + } /* Tell the others */ if(!tunnelserver) { - forward_request(c); + forward_request(c, request); } return true; } +static bool send_sptps_data_myself(void *handle, uint8_t type, const void *data, size_t len) { + return send_sptps_data(handle, myself, type, data, len); +} + +static bool send_initial_sptps_data(void *handle, uint8_t type, const void *data, size_t len) { + (void)type; + node_t *to = handle; + to->sptps.send_data = send_sptps_data_myself; + char buf[len * 4 / 3 + 5]; + + b64encode(data, buf, len); + + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_KEY, buf); +} + bool send_req_key(node_t *to) { + if(to->status.sptps) { + if(!node_read_ecdsa_public_key(to)) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "No Ed25519 key known for %s (%s)", to->name, to->hostname); + send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, REQ_PUBKEY); + return true; + } + + char label[25 + strlen(myself->name) + strlen(to->name)]; + snprintf(label, sizeof(label), "tinc UDP key expansion %s %s", myself->name, to->name); + sptps_stop(&to->sptps); + to->status.validkey = false; + to->status.waitingforkey = true; + to->last_req_key = now.tv_sec; + to->incompression = myself->incompression; + return sptps_start(&to->sptps, to, true, true, myself->connection->ecdsa, to->ecdsa, label, sizeof(label), send_initial_sptps_data, receive_sptps_record); + } + return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name); } -bool req_key_h(connection_t *c) { +/* REQ_KEY is overloaded to allow arbitrary requests to be routed between two nodes. */ + +static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, node_t *to, int reqno) { + (void)c; + + /* If this is a SPTPS packet, see if sending UDP info helps. + Note that we only do this if we're the destination or the static relay; + otherwise every hop would initiate its own UDP info message, resulting in elevated chatter. */ + if((reqno == REQ_KEY || reqno == SPTPS_PACKET) && to->via == myself) { + send_udp_info(myself, from); + } + + if(reqno == SPTPS_PACKET) { + /* This is a SPTPS data packet. */ + + char buf[MAX_STRING_SIZE]; + int len; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s) to %s (%s): %s", "SPTPS_PACKET", from->name, from->hostname, to->name, to->hostname, "invalid SPTPS data"); + return true; + } + + if(to != myself) { + /* We don't just forward the request, because we want to use UDP if it's available. */ + if(forwarding_mode == FMODE_INTERNAL) { + send_sptps_data(to, from, 0, buf, len); + try_tx(to, true); + } + } else { + /* The packet is for us */ + if(!sptps_receive_data(&from->sptps, buf, len)) { + /* Uh-oh. It might be that the tunnel is stuck in some corrupted state, + so let's restart SPTPS in case that helps. But don't do that too often + to prevent storms. */ + if(from->last_req_key < now.tv_sec - 10) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Failed to decode TCP packet from %s (%s), restarting SPTPS", from->name, from->hostname); + send_req_key(from); + } + + return true; + } + + send_mtu_info(myself, from, MTU); + } + + return true; + } + + /* Requests that are not SPTPS data packets are forwarded as-is. */ + + if(to != myself) { + return send_request(to->nexthop->connection, "%s", request); + } + + /* The request is for us */ + + switch(reqno) { + case REQ_PUBKEY: { + if(!node_read_ecdsa_public_key(from)) { + /* Request their key *before* we send our key back. Otherwise the first SPTPS packet from them will get dropped. */ + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Preemptively requesting Ed25519 key for %s (%s)", from->name, from->hostname); + send_request(from->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, from->name, REQ_PUBKEY); + } + + char *pubkey = ecdsa_get_base64_public_key(myself->connection->ecdsa); + send_request(from->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, from->name, ANS_PUBKEY, pubkey); + free(pubkey); + return true; + } + + case ANS_PUBKEY: { + if(node_read_ecdsa_public_key(from)) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got ANS_PUBKEY from %s (%s) even though we already have his pubkey", from->name, from->hostname); + return true; + } + + char pubkey[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !(from->ecdsa = ecdsa_set_base64_public_key(pubkey))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey"); + return true; + } + + logger(DEBUG_PROTOCOL, LOG_INFO, "Learned Ed25519 public key from %s (%s)", from->name, from->hostname); + append_config_file(from->name, "Ed25519PublicKey", pubkey); + return true; + } + + case REQ_KEY: { + if(!node_read_ecdsa_public_key(from)) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "No Ed25519 key known for %s (%s)", from->name, from->hostname); + send_request(from->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, from->name, REQ_PUBKEY); + return true; + } + + if(from->sptps.label) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Got REQ_KEY from %s while we already started a SPTPS session!", from->name); + } + + char buf[MAX_STRING_SIZE]; + size_t len; + + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1 || !(len = b64decode(buf, buf, strlen(buf)))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_SPTPS_START", from->name, from->hostname, "invalid SPTPS data"); + return true; + } + + char label[25 + strlen(from->name) + strlen(myself->name)]; + snprintf(label, sizeof(label), "tinc UDP key expansion %s %s", from->name, myself->name); + sptps_stop(&from->sptps); + from->status.validkey = false; + from->status.waitingforkey = true; + from->last_req_key = now.tv_sec; + sptps_start(&from->sptps, from, false, true, myself->connection->ecdsa, from->ecdsa, label, sizeof(label), send_sptps_data_myself, receive_sptps_record); + sptps_receive_data(&from->sptps, buf, len); + send_mtu_info(myself, from, MTU); + return true; + } + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request); + return true; + } +} + +bool req_key_h(connection_t *c, const char *request) { char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; node_t *from, *to; + int reqno = 0; - if(sscanf(c->buffer, "%*d " MAX_STRING " " MAX_STRING, from_name, to_name) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, + if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &reqno) < 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(from_name); if(!from) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, from_name); return true; } @@ -123,77 +287,108 @@ bool req_key_h(connection_t *c) { to = lookup_node(to_name); if(!to) { - logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, to_name); return true; } /* Check if this key request is for us */ - if(to == myself) { /* Yes, send our own key back */ - if(!send_ans_key(from)) { - return false; + if(to == myself) { /* Yes */ + /* Is this an extended REQ_KEY message? */ + if(experimental && reqno) { + return req_key_ext_h(c, request, from, to, reqno); } + + /* No, just send our key back */ + send_ans_key(from); } else { if(tunnelserver) { return true; } if(!to->status.reachable) { - logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "REQ_KEY", c->name, c->hostname, to_name); return true; } - send_request(to->nexthop->connection, "%s", c->buffer); + /* Is this an extended REQ_KEY message? */ + if(experimental && reqno) { + return req_key_ext_h(c, request, from, to, reqno); + } + + send_request(to->nexthop->connection, "%s", request); } return true; } bool send_ans_key(node_t *to) { - // Set key parameters - to->incipher = myself->incipher; - to->inkeylength = myself->inkeylength; - to->indigest = myself->indigest; - to->inmaclength = myself->inmaclength; + if(to->status.sptps) { + abort(); + } + +#ifdef DISABLE_LEGACY + return false; +#else + size_t keylen = myself->incipher ? cipher_keylength(myself->incipher) : 1; + char key[keylen * 2 + 1]; + + randomize(key, keylen); + + cipher_close(to->incipher); + digest_close(to->indigest); + + if(myself->incipher) { + to->incipher = cipher_open_by_nid(cipher_get_nid(myself->incipher)); + + if(!to->incipher) { + abort(); + } + + if(!cipher_set_key(to->incipher, key, false)) { + abort(); + } + } + + if(myself->indigest) { + to->indigest = digest_open_by_nid(digest_get_nid(myself->indigest), digest_length(myself->indigest)); + + if(!to->indigest) { + abort(); + } + + if(!digest_set_key(to->indigest, key, keylen)) { + abort(); + } + } + to->incompression = myself->incompression; - // Allocate memory for key - to->inkey = xrealloc(to->inkey, to->inkeylength); - - // Create a new key - if(1 != RAND_bytes((unsigned char *)to->inkey, to->inkeylength)) { - int err = ERR_get_error(); - logger(LOG_ERR, "Failed to generate random for key (%s)", ERR_error_string(err, NULL)); - return false; // Do not send insecure keys, let connection attempt fail. - } - - if(to->incipher) { - EVP_DecryptInit_ex(to->inctx, to->incipher, NULL, (unsigned char *)to->inkey, (unsigned char *)to->inkey + EVP_CIPHER_key_length(to->incipher)); - } + bin2hex(key, key, keylen); // Reset sequence number and late packet window mykeyused = true; to->received_seqno = 0; + to->received = 0; if(replaywin) { memset(to->late, 0, replaywin); } - // Convert to hexadecimal and send - char key[2 * to->inkeylength + 1]; - bin2hex(to->inkey, key, to->inkeylength); - key[to->inkeylength * 2] = '\0'; + to->status.validkey_in = true; return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY, myself->name, to->name, key, - to->incipher ? EVP_CIPHER_nid(to->incipher) : 0, - to->indigest ? EVP_MD_type(to->indigest) : 0, to->inmaclength, + cipher_get_nid(to->incipher), + digest_get_nid(to->indigest), + (int)digest_length(to->indigest), to->incompression); +#endif } -bool ans_key_h(connection_t *c) { +bool ans_key_h(connection_t *c, const char *request) { char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; char key[MAX_STRING_SIZE]; @@ -202,23 +397,23 @@ bool ans_key_h(connection_t *c) { int cipher, digest, maclength, compression; node_t *from, *to; - if(sscanf(c->buffer, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING, + if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING, from_name, to_name, key, &cipher, &digest, &maclength, &compression, address, port) < 7) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(from_name); if(!from) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, from_name); return true; } @@ -226,7 +421,7 @@ bool ans_key_h(connection_t *c) { to = lookup_node(to_name); if(!to) { - logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, to_name); return true; } @@ -239,107 +434,131 @@ bool ans_key_h(connection_t *c) { } if(!to->status.reachable) { - logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(DEBUG_ALWAYS, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "ANS_KEY", c->name, c->hostname, to_name); return true; } if(!*address && from->address.sa.sa_family != AF_UNSPEC && to->minmtu) { char *address, *port; - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); sockaddr2str(&from->address, &address, &port); - send_request(to->nexthop->connection, "%s %s %s", c->buffer, address, port); + send_request(to->nexthop->connection, "%s %s %s", request, address, port); free(address); free(port); return true; } - return send_request(to->nexthop->connection, "%s", c->buffer); + return send_request(to->nexthop->connection, "%s", request); } +#ifndef DISABLE_LEGACY /* Don't use key material until every check has passed. */ - from->status.validkey = false; + cipher_close(from->outcipher); + digest_close(from->outdigest); +#endif - /* Update our copy of the origin's packet key */ - from->outkey = xrealloc(from->outkey, strlen(key) / 2); - from->outkeylength = strlen(key) / 2; - - if(!hex2bin(key, from->outkey, from->outkeylength)) { - logger(LOG_ERR, "Got bad %s from %s(%s): %s", "ANS_KEY", from->name, from->hostname, "invalid key"); - return true; - } - - /* Check and lookup cipher and digest algorithms */ - - if(cipher) { - from->outcipher = EVP_get_cipherbynid(cipher); - - if(!from->outcipher) { - logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, - from->hostname); - return true; - } - - if(from->outkeylength != EVP_CIPHER_key_length(from->outcipher) + EVP_CIPHER_iv_length(from->outcipher)) { - logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, - from->hostname); - return true; - } - } else { - if(from->outkeylength != 1) { - logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); - return true; - } - - from->outcipher = NULL; - } - - from->outmaclength = maclength; - - if(digest) { - from->outdigest = EVP_get_digestbynid(digest); - - if(!from->outdigest) { - logger(LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, - from->hostname); - return true; - } - - if(from->outmaclength > EVP_MD_size(from->outdigest) || from->outmaclength < 0) { - logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!", - from->name, from->hostname); - return true; - } - } else { - from->outdigest = NULL; + if(!from->status.sptps) { + from->status.validkey = false; } if(compression < 0 || compression > 11) { - logger(LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); return true; } from->outcompression = compression; - if(from->outcipher) - if(!EVP_EncryptInit_ex(from->outctx, from->outcipher, NULL, (unsigned char *)from->outkey, (unsigned char *)from->outkey + EVP_CIPHER_key_length(from->outcipher))) { - logger(LOG_ERR, "Error during initialisation of key from %s (%s): %s", - from->name, from->hostname, ERR_error_string(ERR_get_error(), NULL)); + /* SPTPS or old-style key exchange? */ + + if(from->status.sptps) { + char buf[strlen(key)]; + size_t len = b64decode(key, buf, strlen(key)); + + if(!len || !sptps_receive_data(&from->sptps, buf, len)) { + /* Uh-oh. It might be that the tunnel is stuck in some corrupted state, + so let's restart SPTPS in case that helps. But don't do that too often + to prevent storms. + Note that simply relying on handshake timeout is not enough, because + that doesn't apply to key regeneration. */ + if(from->last_req_key < now.tv_sec - 10) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Failed to decode handshake TCP packet from %s (%s), restarting SPTPS", from->name, from->hostname); + send_req_key(from); + } + return true; } + if(from->status.validkey) { + if(*address && *port) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + sockaddr_t sa = str2sockaddr(address, port); + update_node_udp(from, &sa); + } + } + + send_mtu_info(myself, from, MTU); + + return true; + } + +#ifdef DISABLE_LEGACY + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses legacy protocol!", from->name, from->hostname); + return false; +#else + /* Check and lookup cipher and digest algorithms */ + + if(cipher) { + if(!(from->outcipher = cipher_open_by_nid(cipher))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname); + return false; + } + } else { + from->outcipher = NULL; + } + + if(digest) { + if(!(from->outdigest = digest_open_by_nid(digest, maclength))) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, from->hostname); + return false; + } + } else { + from->outdigest = NULL; + } + + if((size_t)maclength != digest_length(from->outdigest)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses bogus MAC length!", from->name, from->hostname); + return false; + } + + /* Process key */ + + size_t keylen = hex2bin(key, key, sizeof(key)); + + if(keylen != (from->outcipher ? cipher_keylength(from->outcipher) : 1)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); + return true; + } + + /* Update our copy of the origin's packet key */ + + if(from->outcipher && !cipher_set_key(from->outcipher, key, true)) { + return false; + } + + if(from->outdigest && !digest_set_key(from->outdigest, key, keylen)) { + return false; + } + from->status.validkey = true; from->sent_seqno = 0; if(*address && *port) { - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); sockaddr_t sa = str2sockaddr(address, port); update_node_udp(from, &sa); } - if(from->options & OPTION_PMTU_DISCOVERY && !from->mtuevent) { - send_mtu_probe(from); - } - return true; +#endif } diff --git a/src/protocol_misc.c b/src/protocol_misc.c index 904ac3f..050e30a 100644 --- a/src/protocol_misc.c +++ b/src/protocol_misc.c @@ -1,7 +1,7 @@ /* protocol_misc.c -- handle the meta-protocol, miscellaneous functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2012 Guus Sliepen + 2000-2013 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 @@ -20,6 +20,7 @@ #include "system.h" +#include "address_cache.h" #include "conf.h" #include "connection.h" #include "logger.h" @@ -28,17 +29,35 @@ #include "netutl.h" #include "protocol.h" #include "utils.h" +#include "xalloc.h" + +#ifndef MIN +#define MIN(x, y) (((x)<(y))?(x):(y)) +#endif int maxoutbufsize = 0; +int mtu_info_interval = 5; +int udp_info_interval = 5; + +bool send_termreq(connection_t *c) { + return send_request(c, "%d", TERMREQ); +} + +bool termreq_h(connection_t *c, const char *request) { + (void)c; + (void)request; + return false; +} bool send_ping(connection_t *c) { c->status.pinged = true; - c->last_ping_time = now; + c->last_ping_time = now.tv_sec; return send_request(c, "%d", PING); } -bool ping_h(connection_t *c) { +bool ping_h(connection_t *c, const char *request) { + (void)request; return send_pong(c); } @@ -46,21 +65,15 @@ bool send_pong(connection_t *c) { return send_request(c, "%d", PONG); } -bool pong_h(connection_t *c) { +bool pong_h(connection_t *c, const char *request) { + (void)request; c->status.pinged = false; /* Successful connection, reset timeout if this is an outgoing connection. */ - if(c->outgoing) { + if(c->outgoing && c->outgoing->timeout) { c->outgoing->timeout = 0; - c->outgoing->cfg = NULL; - - if(c->outgoing->ai) { - freeaddrinfo(c->outgoing->ai); - } - - c->outgoing->ai = NULL; - c->outgoing->aip = NULL; + reset_address_cache(c->outgoing->node->address_cache, &c->address); } return true; @@ -72,22 +85,22 @@ bool send_tcppacket(connection_t *c, const vpn_packet_t *packet) { /* If there already is a lot of data in the outbuf buffer, discard this packet. We use a very simple Random Early Drop algorithm. */ - if(2.0 * c->outbuflen / (float)maxoutbufsize - 1 > (float)rand() / (float)RAND_MAX) { + if(2.0 * c->outbuf.len / (float)maxoutbufsize - 1 > (float)rand() / (float)RAND_MAX) { return true; } - if(!send_request(c, "%d %hd", PACKET, packet->len)) { + if(!send_request(c, "%d %d", PACKET, packet->len)) { return false; } - return send_meta(c, (char *)packet->data, packet->len) && flush_meta(c); + return send_meta(c, (char *)DATA(packet), packet->len); } -bool tcppacket_h(connection_t *c) { - length_t len; +bool tcppacket_h(connection_t *c, const char *request) { + short int len; - if(sscanf(c->buffer, "%*d %hu", &len) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name, + if(sscanf(request, "%*d %hd", &len) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name, c->hostname); return false; } @@ -98,3 +111,260 @@ bool tcppacket_h(connection_t *c) { return true; } + +bool send_sptps_tcppacket(connection_t *c, const char *packet, int len) { + /* If there already is a lot of data in the outbuf buffer, discard this packet. + We use a very simple Random Early Drop algorithm. */ + + if(2.0 * c->outbuf.len / (float)maxoutbufsize - 1 > (float)rand() / (float)RAND_MAX) { + return true; + } + + if(!send_request(c, "%d %d", SPTPS_PACKET, len)) { + return false; + } + + send_meta_raw(c, packet, len); + return true; +} + +bool sptps_tcppacket_h(connection_t *c, const char *request) { + short int len; + + if(sscanf(request, "%*d %hd", &len) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "SPTPS_PACKET", c->name, + c->hostname); + return false; + } + + /* Set sptpslen to len, this will tell receive_meta() that a SPTPS packet is coming. */ + + c->sptpslen = len; + + return true; +} + +/* Transmitting UDP information */ + +bool send_udp_info(node_t *from, node_t *to) { + /* If there's a static relay in the path, there's no point in sending the message + farther than the static relay. */ + to = (to->via == myself) ? to->nexthop : to->via; + + if(to == NULL) { + logger(DEBUG_ALWAYS, LOG_ERR, "Something went wrong when selecting relay - possible fake UDP_INFO"); + return false; + } + + /* Skip cases where sending UDP info messages doesn't make sense. + This is done here in order to avoid repeating the same logic in multiple callsites. */ + + if(to == myself) { + return true; + } + + if(!to->status.reachable) { + return true; + } + + if(from == myself) { + if(to->connection) { + return true; + } + + struct timeval elapsed; + + timersub(&now, &to->udp_info_sent, &elapsed); + + if(elapsed.tv_sec < udp_info_interval) { + return true; + } + } + + if((myself->options | from->options | to->options) & OPTION_TCPONLY) { + return true; + } + + if((to->nexthop->options >> 24) < 5) { + return true; + } + + char *from_address, *from_port; + /* If we're the originator, the address we use is irrelevant + because the first intermediate node will ignore it. + We use our local address as it somewhat makes sense + and it's simpler than introducing an encoding for "null" addresses anyway. */ + sockaddr2str((from != myself) ? &from->address : &to->nexthop->connection->edge->local_address, &from_address, &from_port); + + bool x = send_request(to->nexthop->connection, "%d %s %s %s %s", UDP_INFO, from->name, to->name, from_address, from_port); + + free(from_address); + free(from_port); + + if(from == myself) { + to->udp_info_sent = now; + } + + return x; +} + +bool udp_info_h(connection_t *c, const char *request) { + char from_name[MAX_STRING_SIZE]; + char to_name[MAX_STRING_SIZE]; + char from_address[MAX_STRING_SIZE]; + char from_port[MAX_STRING_SIZE]; + + if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING, from_name, to_name, from_address, from_port) != 4) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "UDP_INFO", c->name, c->hostname); + return false; + } + + if(!check_id(from_name) || !check_id(to_name)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "UDP_INFO", c->name, c->hostname, "invalid name"); + return false; + } + + node_t *from = lookup_node(from_name); + + if(!from) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "UDP_INFO", c->name, c->hostname, from_name); + return true; + } + + if(from != from->via) { + /* Not supposed to happen, as it means the message wandered past a static relay */ + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got UDP info message from %s (%s) which we can't reach directly", from->name, from->hostname); + return true; + } + + /* If we have a direct edge to "from", we are in a better position + to guess its address than it is itself. */ + if(!from->connection && !from->status.udp_confirmed) { + sockaddr_t from_addr = str2sockaddr(from_address, from_port); + + if(sockaddrcmp(&from_addr, &from->address)) { + update_node_udp(from, &from_addr); + } + } + + node_t *to = lookup_node(to_name); + + if(!to) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "UDP_INFO", c->name, c->hostname, to_name); + return true; + } + + /* Send our own data (which could be what we just received) up the chain. */ + + return send_udp_info(from, to); +} + +/* Transmitting MTU information */ + +bool send_mtu_info(node_t *from, node_t *to, int mtu) { + /* Skip cases where sending MTU info messages doesn't make sense. + This is done here in order to avoid repeating the same logic in multiple callsites. */ + + if(to == myself) { + return true; + } + + if(!to->status.reachable) { + return true; + } + + if(from == myself) { + if(to->connection) { + return true; + } + + struct timeval elapsed; + + timersub(&now, &to->mtu_info_sent, &elapsed); + + if(elapsed.tv_sec < mtu_info_interval) { + return true; + } + } + + if((to->nexthop->options >> 24) < 6) { + return true; + } + + /* We will send the passed-in MTU value, unless we believe ours is better. */ + + node_t *via = (from->via == myself) ? from->nexthop : from->via; + + if(from->minmtu == from->maxmtu && from->via == myself) { + /* We have a direct measurement. Override the value entirely. + Note that we only do that if we are sitting as a static relay in the path; + otherwise, we can't guarantee packets will flow through us, and increasing + MTU could therefore end up being too optimistic. */ + mtu = from->minmtu; + } else if(via->minmtu == via->maxmtu) { + /* Static relay. Ensure packets will make it through the entire relay path. */ + mtu = MIN(mtu, via->minmtu); + } else if(via->nexthop->minmtu == via->nexthop->maxmtu) { + /* Dynamic relay. Ensure packets will make it through the entire relay path. */ + mtu = MIN(mtu, via->nexthop->minmtu); + } + + if(from == myself) { + to->mtu_info_sent = now; + } + + /* If none of the conditions above match in the steady state, it means we're using TCP, + so the MTU is irrelevant. That said, it is still important to honor the MTU that was passed in, + because other parts of the relay path might be able to use UDP, which means they care about the MTU. */ + + return send_request(to->nexthop->connection, "%d %s %s %d", MTU_INFO, from->name, to->name, mtu); +} + +bool mtu_info_h(connection_t *c, const char *request) { + char from_name[MAX_STRING_SIZE]; + char to_name[MAX_STRING_SIZE]; + int mtu; + + if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" %d", from_name, to_name, &mtu) != 3) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "MTU_INFO", c->name, c->hostname); + return false; + } + + if(mtu < 512) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "MTU_INFO", c->name, c->hostname, "invalid MTU"); + return false; + } + + mtu = MIN(mtu, MTU); + + if(!check_id(from_name) || !check_id(to_name)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "MTU_INFO", c->name, c->hostname, "invalid name"); + return false; + } + + node_t *from = lookup_node(from_name); + + if(!from) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "MTU_INFO", c->name, c->hostname, from_name); + return true; + } + + /* If we don't know the current MTU for that node, use the one we received. + Even if we're about to make our own measurements, the value we got from downstream nodes should be pretty close + so it's a good idea to use it in the mean time. */ + if(from->mtu != mtu && from->minmtu != from->maxmtu) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Using provisional MTU %d for node %s (%s)", mtu, from->name, from->hostname); + from->mtu = mtu; + } + + node_t *to = lookup_node(to_name); + + if(!to) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "MTU_INFO", c->name, c->hostname, to_name); + return true; + } + + /* Continue passing the MTU value (or a better one if we have it) up the chain. */ + + return send_mtu_info(from, to, mtu); +} diff --git a/src/protocol_subnet.c b/src/protocol_subnet.c index 2c4d08f..53afb8a 100644 --- a/src/protocol_subnet.c +++ b/src/protocol_subnet.c @@ -1,7 +1,7 @@ /* protocol_subnet.c -- handle the meta-protocol, subnets Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen 2009 Michael Tokarev This program is free software; you can redistribute it and/or modify @@ -42,14 +42,14 @@ bool send_add_subnet(connection_t *c, const subnet_t *subnet) { return send_request(c, "%d %x %s %s", ADD_SUBNET, rand(), subnet->owner->name, netstr); } -bool add_subnet_h(connection_t *c) { +bool add_subnet_h(connection_t *c, const char *request) { char subnetstr[MAX_STRING_SIZE]; char name[MAX_STRING_SIZE]; node_t *owner; subnet_t s = {0}, *new, *old; - if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_SUBNET", c->name, + if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_SUBNET", c->name, c->hostname); return false; } @@ -57,7 +57,7 @@ bool add_subnet_h(connection_t *c) { /* Check if owner name is valid */ if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, "invalid name"); return false; } @@ -65,12 +65,12 @@ bool add_subnet_h(connection_t *c) { /* Check if subnet string is valid */ if(!str2net(&s, subnetstr)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, "invalid subnet string"); return false; } - if(seen_request(c->buffer)) { + if(seen_request(request)) { return true; } @@ -80,8 +80,8 @@ bool add_subnet_h(connection_t *c) { if(tunnelserver && owner != myself && owner != c->node) { /* in case of tunnelserver, ignore indirect subnet registrations */ - ifdebug(PROTOCOL) logger(LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", - "ADD_SUBNET", c->name, c->hostname, subnetstr); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", + "ADD_SUBNET", c->name, c->hostname, subnetstr); return true; } @@ -100,8 +100,8 @@ bool add_subnet_h(connection_t *c) { /* If we don't know this subnet, but we are the owner, retaliate with a DEL_SUBNET */ if(owner == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", - "ADD_SUBNET", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", + "ADD_SUBNET", c->name, c->hostname); s.owner = myself; send_del_subnet(c, &s); return true; @@ -110,7 +110,7 @@ bool add_subnet_h(connection_t *c) { /* In tunnel server mode, we should already know all allowed subnets */ if(tunnelserver) { - logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, subnetstr); return true; } @@ -118,9 +118,9 @@ bool add_subnet_h(connection_t *c) { /* Ignore if strictsubnets is true, but forward it to others */ if(strictsubnets) { - logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, subnetstr); - forward_request(c); + forward_request(c, request); return true; } @@ -135,12 +135,14 @@ bool add_subnet_h(connection_t *c) { /* Tell the rest */ - forward_request(c); + if(!tunnelserver) { + forward_request(c, request); + } /* Fast handoff of roaming MAC addresses */ if(s.type == SUBNET_MAC && owner != myself && (old = lookup_subnet(myself, &s)) && old->expires) { - old->expires = now; + old->expires = 1; } return true; @@ -156,14 +158,14 @@ bool send_del_subnet(connection_t *c, const subnet_t *s) { return send_request(c, "%d %x %s %s", DEL_SUBNET, rand(), s->owner->name, netstr); } -bool del_subnet_h(connection_t *c) { +bool del_subnet_h(connection_t *c, const char *request) { char subnetstr[MAX_STRING_SIZE]; char name[MAX_STRING_SIZE]; node_t *owner; subnet_t s = {0}, *find; - if(sscanf(c->buffer, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_SUBNET", c->name, + if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_SUBNET", c->name, c->hostname); return false; } @@ -171,7 +173,7 @@ bool del_subnet_h(connection_t *c) { /* Check if owner name is valid */ if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, c->hostname, "invalid name"); return false; } @@ -179,12 +181,12 @@ bool del_subnet_h(connection_t *c) { /* Check if subnet string is valid */ if(!str2net(&s, subnetstr)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, c->hostname, "invalid subnet string"); return false; } - if(seen_request(c->buffer)) { + if(seen_request(request)) { return true; } @@ -194,14 +196,14 @@ bool del_subnet_h(connection_t *c) { if(tunnelserver && owner != myself && owner != c->node) { /* in case of tunnelserver, ignore indirect subnet deletion */ - ifdebug(PROTOCOL) logger(LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", - "DEL_SUBNET", c->name, c->hostname, subnetstr); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", + "DEL_SUBNET", c->name, c->hostname, subnetstr); return true; } if(!owner) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for %s which is not in our node tree", - "DEL_SUBNET", c->name, c->hostname, name); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for %s which is not in our node tree", + "DEL_SUBNET", c->name, c->hostname, name); return true; } @@ -212,11 +214,11 @@ bool del_subnet_h(connection_t *c) { find = lookup_subnet(owner, &s); if(!find) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for %s which does not appear in his subnet tree", - "DEL_SUBNET", c->name, c->hostname, name); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for %s which does not appear in his subnet tree", + "DEL_SUBNET", c->name, c->hostname, name); if(strictsubnets) { - forward_request(c); + forward_request(c, request); } return true; @@ -225,8 +227,8 @@ bool del_subnet_h(connection_t *c) { /* If we are the owner of this subnet, retaliate with an ADD_SUBNET */ if(owner == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", - "DEL_SUBNET", c->name, c->hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", + "DEL_SUBNET", c->name, c->hostname); send_add_subnet(c, find); return true; } @@ -237,7 +239,9 @@ bool del_subnet_h(connection_t *c) { /* Tell the rest */ - forward_request(c); + if(!tunnelserver) { + forward_request(c, request); + } if(strictsubnets) { return true; diff --git a/src/proxy.c b/src/proxy.c deleted file mode 100644 index 979626c..0000000 --- a/src/proxy.c +++ /dev/null @@ -1,366 +0,0 @@ -/* - proxy.c -- Proxy handling functions. - Copyright (C) 2015-2017 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 "connection.h" -#include "logger.h" -#include "meta.h" -#include "netutl.h" -#include "protocol.h" -#include "proxy.h" -#include "utils.h" // - -proxytype_t proxytype; -char *proxyhost; -char *proxyport; -char *proxyuser; -char *proxypass; - -static void update_address_ipv4(connection_t *c, void *address, void *port) { - sockaddrfree(&c->address); - memset(&c->address, 0, sizeof(c->address)); - c->address.sa.sa_family = AF_INET; - - if(address) { - memcpy(&c->address.in.sin_addr, address, sizeof(ipv4_t)); - } - - if(port) { - memcpy(&c->address.in.sin_port, port, sizeof(uint16_t)); - } - - // OpenSSH -D returns all zero address, set it to 0.0.0.1 to prevent spamming ourselves. - if(!memcmp(&c->address.in.sin_addr, "\0\0\0\0", 4)) { - memcpy(&c->address.in.sin_addr, "\0\0\0\01", 4); - } -} - -static void update_address_ipv6(connection_t *c, void *address, void *port) { - sockaddrfree(&c->address); - memset(&c->address, 0, sizeof(c->address)); - c->address.sa.sa_family = AF_INET6; - - if(address) { - memcpy(&c->address.in6.sin6_addr, address, sizeof(ipv6_t)); - } - - if(port) { - memcpy(&c->address.in6.sin6_port, port, sizeof(uint16_t)); - } - - // OpenSSH -D returns all zero address, set it to 0100:: to prevent spamming ourselves. - if(!memcmp(&c->address.in6.sin6_addr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16)) { - memcpy(&c->address.in6.sin6_addr, "\01\0\0\0\0\0\0\0", 8); - } -} - -bool send_proxyrequest(connection_t *c) { - switch(proxytype) { - case PROXY_SOCKS4: - if(c->address.sa.sa_family != AF_INET) { - logger(LOG_ERR, "Can only connect to numeric IPv4 addresses through a SOCKS 4 proxy!"); - return false; - } - - // fallthrough - case PROXY_SOCKS4A: { - if(c->address.sa.sa_family != AF_INET && c->address.sa.sa_family != AF_UNKNOWN) { - logger(LOG_ERR, "Can only connect to IPv4 addresses or hostnames through a SOCKS 4a proxy!"); - return false; - } - - int len = 9; - - if(proxyuser) { - len += strlen(proxyuser); - } - - if(c->address.sa.sa_family == AF_UNKNOWN) { - len += 1 + strlen(c->address.unknown.address); - } - - char s4req[len]; - s4req[0] = 4; - s4req[1] = 1; - - if(c->address.sa.sa_family == AF_INET) { - memcpy(s4req + 2, &c->address.in.sin_port, 2); - memcpy(s4req + 4, &c->address.in.sin_addr, 4); - } else { - uint16_t port = htons(atoi(c->address.unknown.port)); - memcpy(s4req + 2, &port, 2); - memcpy(s4req + 4, "\0\0\0\1", 4); - strcpy(s4req + (9 + (proxyuser ? strlen(proxyuser) : 0)), c->address.unknown.address); - } - - if(proxyuser) { - strcpy(s4req + 8, proxyuser); - } else { - s4req[8] = 0; - } - - s4req[sizeof(s4req) - 1] = 0; - c->allow_request = PROXY; - return send_meta(c, s4req, sizeof(s4req)); - } - - case PROXY_SOCKS5: { - int len = 3 + 6; - - if(c->address.sa.sa_family == AF_INET) { - len += 4; - } else if(c->address.sa.sa_family == AF_INET6) { - len += 16; - } else if(c->address.sa.sa_family == AF_UNKNOWN) { - len += 1 + strlen(c->address.unknown.address); - } else { - logger(LOG_ERR, "Address family %x not supported for SOCKS 5 proxies!", c->address.sa.sa_family); - return false; - } - - if(proxypass) { - len += 3 + strlen(proxyuser) + strlen(proxypass); - } - - char s5req[len]; - int i = 0; - s5req[i++] = 5; - s5req[i++] = 1; - - if(proxypass) { - s5req[i++] = 2; - s5req[i++] = 1; - s5req[i++] = strlen(proxyuser); - strcpy(s5req + i, proxyuser); - i += strlen(proxyuser); - s5req[i++] = strlen(proxypass); - strcpy(s5req + i, proxypass); - i += strlen(proxypass); - } else { - s5req[i++] = 0; - } - - s5req[i++] = 5; - s5req[i++] = 1; - s5req[i++] = 0; - - if(c->address.sa.sa_family == AF_INET) { - s5req[i++] = 1; - memcpy(s5req + i, &c->address.in.sin_addr, 4); - i += 4; - memcpy(s5req + i, &c->address.in.sin_port, 2); - i += 2; - } else if(c->address.sa.sa_family == AF_INET6) { - s5req[i++] = 4; - memcpy(s5req + i, &c->address.in6.sin6_addr, 16); - i += 16; - memcpy(s5req + i, &c->address.in6.sin6_port, 2); - i += 2; - } else if(c->address.sa.sa_family == AF_UNKNOWN) { - s5req[i++] = 3; - int len = strlen(c->address.unknown.address); - s5req[i++] = len; - memcpy(s5req + i, c->address.unknown.address, len); - i += len; - uint16_t port = htons(atoi(c->address.unknown.port)); - memcpy(s5req + i, &port, 2); - i += 2; - } else { - logger(LOG_ERR, "Unknown address family while trying to connect to SOCKS5 proxy"); - return false; - } - - if(i > len) { - abort(); - } - - c->allow_request = PROXY; - return send_meta(c, s5req, sizeof(s5req)); - } - - case PROXY_HTTP: { - char *host; - char *port; - - sockaddr2str(&c->address, &host, &port); - send_request(c, "CONNECT %s:%s HTTP/1.1\r\n\r", host, port); - free(host); - free(port); - c->allow_request = PROXY; - return true; - } - - case PROXY_EXEC: - abort(); - - default: - logger(LOG_ERR, "Unknown proxy type"); - return false; - } -} - -int receive_proxy_meta(connection_t *c) { - switch(proxytype) { - case PROXY_SOCKS4: - case PROXY_SOCKS4A: - if(c->buflen < 8) { - return 0; - } - - if(c->buffer[0] == 0 && c->buffer[1] == 0x5a) { - if(c->address.sa.sa_family == AF_UNKNOWN) { - update_address_ipv4(c, c->buffer + 4, c->buffer + 2); - } - - ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Proxy request granted"); - c->allow_request = ID; - c->status.proxy_passed = true; - send_id(c); - return 8; - } else { - logger(LOG_ERR, "Proxy request rejected"); - return -1; - } - - case PROXY_SOCKS5: - if(c->buflen < 2) { - return 0; - } - - if(c->buffer[0] != 0x05 || c->buffer[1] == (char)0xff) { - logger(LOG_ERR, "Proxy authentication method rejected"); - return -1; - } - - int offset = 2; - - if(c->buffer[1] == 0x02) { - if(c->buflen < 4) { - return 0; - } - - if(c->buffer[2] != 0x05 || c->buffer[3] != 0x00) { - logger(LOG_ERR, "Proxy username/password rejected"); - return -1; - } - - offset += 2; - } - - if(c->buflen - offset < 7) { - return 0; - } - - if(c->buffer[offset] != 0x05 || c->buffer[offset + 1] != 0x00) { - logger(LOG_ERR, "Proxy request rejected"); - return -1; - } - - int replen = offset + 6; - - switch(c->buffer[offset + 3]) { - case 0x01: // IPv4 - if(c->address.sa.sa_family == AF_UNKNOWN) { - update_address_ipv4(c, c->buffer + offset + 4, c->buffer + offset + 8); - } - - replen += 4; - break; - - case 0x03: // Hostname - if(c->address.sa.sa_family == AF_UNKNOWN) { - update_address_ipv4(c, "\0\0\0\1", "\0\0"); - } - - replen += ((uint8_t *)c->buffer)[offset + 4]; - break; - - case 0x04: // IPv6 - if(c->address.sa.sa_family == AF_UNKNOWN) { - update_address_ipv6(c, c->buffer + offset + 4, c->buffer + offset + 20); - } - - replen += 16; - break; - - default: - logger(LOG_ERR, "Proxy reply malformed"); - return -1; - } - - if(c->buflen < replen) { - return 0; - } else { - ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Proxy request granted"); - c->allow_request = ID; - c->status.proxy_passed = true; - send_id(c); - return replen; - } - - case PROXY_HTTP: { - char *p = memchr(c->buffer, '\n', c->buflen); - - if(!p || p - c->buffer >= c->buflen) { - return 0; - } - - while((p = memchr(p + 1, '\n', c->buflen - (p + 1 - c->buffer)))) { - if(p > c->buffer + 3 && !memcmp(p - 3, "\r\n\r\n", 4)) { - break; - } - } - - if(!p) { - return 0; - } - - if(c->buflen < 9) { - return 0; - } - - if(!strncasecmp(c->buffer, "HTTP/1.1 ", 9)) { - if(!strncmp(c->buffer + 9, "200", 3)) { - if(c->address.sa.sa_family == AF_UNKNOWN) { - update_address_ipv4(c, "\0\0\0\1", "\0\0"); - } - - logger(LOG_DEBUG, "Proxy request granted"); - replen = p + 1 - c->buffer; - c->allow_request = ID; - c->status.proxy_passed = true; - send_id(c); - return replen; - } else { - p = memchr(c->buffer, '\n', c->buflen); - p[-1] = 0; - logger(LOG_ERR, "Proxy request rejected: %s", c->buffer + 9); - return false; - } - } else { - logger(LOG_ERR, "Proxy reply malformed"); - return -1; - } - } - - default: - abort(); - } -} diff --git a/src/raw_socket_device.c b/src/raw_socket_device.c index f4ed694..02f6afa 100644 --- a/src/raw_socket_device.c +++ b/src/raw_socket_device.c @@ -1,7 +1,7 @@ /* device.c -- raw socket Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2014 Guus Sliepen + 2002-2012 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 @@ -35,9 +35,6 @@ #if defined(PF_PACKET) && defined(ETH_P_ALL) && defined(AF_PACKET) && defined(SIOCGIFINDEX) static const char *device_info = "raw_socket"; -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - static bool setup_device(void) { struct ifreq ifr; struct sockaddr_ll sa; @@ -51,99 +48,94 @@ static bool setup_device(void) { } if((device_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) { - logger(LOG_ERR, "Could not open %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info, strerror(errno)); return false; } + memset(&ifr, 0, sizeof(ifr)); + #ifdef FD_CLOEXEC fcntl(device_fd, F_SETFD, FD_CLOEXEC); #endif - memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0; if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) { close(device_fd); - logger(LOG_ERR, "Can't find interface %s: %s", ifr.ifr_ifrn.ifrn_name, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't find interface %s: %s", iface, + strerror(errno)); return false; } - memset(&sa, 0, sizeof(sa)); + memset(&sa, '0', sizeof(sa)); sa.sll_family = AF_PACKET; sa.sll_protocol = htons(ETH_P_ALL); sa.sll_ifindex = ifr.ifr_ifindex; if(bind(device_fd, (struct sockaddr *) &sa, (socklen_t) sizeof(sa))) { - logger(LOG_ERR, "Could not bind %s to %s: %s", device, ifr.ifr_ifrn.ifrn_name, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind %s to %s: %s", device, iface, strerror(errno)); return false; } - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } static void close_device(void) { close(device_fd); + device_fd = -1; free(device); + device = NULL; free(iface); + iface = NULL; + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { - int lenin; + int inlen; - if((lenin = read(device_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + if((inlen = read(device_fd, DATA(packet), MTU)) <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - packet->len = lenin; + packet->len = inlen; - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, - device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); - if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + if(write(device_fd, DATA(packet), packet->len) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t raw_socket_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; #else static bool not_supported(void) { - logger(LOG_ERR, "Raw socket device not supported on this platform"); + logger(DEBUG_ALWAYS, LOG_ERR, "Raw socket device not supported on this platform"); return false; } @@ -152,6 +144,5 @@ const devops_t raw_socket_devops = { .close = NULL, .read = NULL, .write = NULL, - .dump_stats = NULL, }; #endif diff --git a/src/route.c b/src/route.c index f8b11bb..d1048e7 100644 --- a/src/route.c +++ b/src/route.c @@ -1,8 +1,7 @@ /* route.c -- routing Copyright (C) 2000-2005 Ivo Timmermans, - 2000-2017 Guus Sliepen - 2015-2016 Vittorio Gambaletta + 2000-2018 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 @@ -21,12 +20,13 @@ #include "system.h" -#include "avl_tree.h" #include "connection.h" +#include "control_common.h" #include "ethernet.h" #include "ipv4.h" #include "ipv6.h" #include "logger.h" +#include "meta.h" #include "net.h" #include "protocol.h" #include "route.h" @@ -42,6 +42,7 @@ bool priorityinheritance = false; int macexpire = 600; bool overwrite_mac = false; mac_t mymac = {{0xFE, 0xFD, 0, 0, 0, 0}}; +bool pcap = false; /* Sizes of various headers */ @@ -58,19 +59,24 @@ static const size_t opt_size = sizeof(struct nd_opt_hdr); #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif +static timeout_t age_subnets_timeout; + /* RFC 1071 */ -static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) { - uint16_t *p = data; +static uint16_t inet_checksum(void *vdata, int len, uint16_t prevsum) { + uint8_t *data = vdata; + uint16_t word; uint32_t checksum = prevsum ^ 0xFFFF; while(len >= 2) { - checksum += *p++; + memcpy(&word, data, sizeof(word)); + checksum += word; + data += 2; len -= 2; } if(len) { - checksum += *(uint8_t *)p; + checksum += *data; } while(checksum >> 16) { @@ -84,12 +90,12 @@ static bool ratelimit(int frequency) { static time_t lasttime = 0; static int count = 0; - if(lasttime == now) { + if(lasttime == now.tv_sec) { if(count >= frequency) { return true; } } else { - lasttime = now; + lasttime = now.tv_sec; count = 0; } @@ -99,7 +105,7 @@ static bool ratelimit(int frequency) { static bool checklength(node_t *source, vpn_packet_t *packet, length_t length) { if(packet->len < length) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname); return false; } else { return true; @@ -108,9 +114,9 @@ static bool checklength(node_t *source, vpn_packet_t *packet, length_t length) { static void swap_mac_addresses(vpn_packet_t *packet) { mac_t tmp; - memcpy(&tmp, &packet->data[0], sizeof(tmp)); - memcpy(&packet->data[0], &packet->data[6], sizeof(tmp)); - memcpy(&packet->data[6], &tmp, sizeof(tmp)); + memcpy(&tmp, &DATA(packet)[0], sizeof(tmp)); + memcpy(&DATA(packet)[0], &DATA(packet)[6], sizeof(tmp)); + memcpy(&DATA(packet)[6], &tmp, sizeof(tmp)); } /* RFC 792 */ @@ -133,7 +139,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_ /* Copy headers from packet into properly aligned structs on the stack */ - memcpy(&ip, packet->data + ether_size, ip_size); + memcpy(&ip, DATA(packet) + ether_size, ip_size); /* Remember original source and destination */ @@ -156,7 +162,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_ addr.sin_family = AF_INET; socklen_t addrlen = sizeof(addr); - if(!getsockname(sockfd, (struct sockaddr *) &addr, &addrlen) && addrlen <= sizeof(addr)) { + if(!getsockname(sockfd, (struct sockaddr *) &addr, &addrlen) && (size_t)addrlen <= sizeof(addr)) { ip_dst = addr.sin_addr; } } @@ -177,7 +183,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_ /* Copy first part of original contents to ICMP message */ - memmove(packet->data + ether_size + ip_size + icmp_size, packet->data + ether_size, oldlen); + memmove(DATA(packet) + ether_size + ip_size + icmp_size, DATA(packet) + ether_size, oldlen); /* Fill in IPv4 header */ @@ -202,12 +208,12 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_ icmp.icmp_cksum = 0; icmp.icmp_cksum = inet_checksum(&icmp, icmp_size, ~0); - icmp.icmp_cksum = inet_checksum(packet->data + ether_size + ip_size + icmp_size, oldlen, icmp.icmp_cksum); + icmp.icmp_cksum = inet_checksum(DATA(packet) + ether_size + ip_size + icmp_size, oldlen, icmp.icmp_cksum); /* Copy structs on stack back to packet */ - memcpy(packet->data + ether_size, &ip, ip_size); - memcpy(packet->data + ether_size + ip_size, &icmp, icmp_size); + memcpy(DATA(packet) + ether_size, &ip, ip_size); + memcpy(DATA(packet) + ether_size + ip_size, &icmp, icmp_size); packet->len = ether_size + ip_size + icmp_size + oldlen; @@ -238,7 +244,7 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_ /* Copy headers from packet to structs on the stack */ - memcpy(&ip6, packet->data + ether_size, ip6_size); + memcpy(&ip6, DATA(packet) + ether_size, ip6_size); /* Remember original source and destination */ @@ -261,7 +267,7 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_ addr.sin6_family = AF_INET6; socklen_t addrlen = sizeof(addr); - if(!getsockname(sockfd, (struct sockaddr *) &addr, &addrlen) && addrlen <= sizeof(addr)) { + if(!getsockname(sockfd, (struct sockaddr *) &addr, &addrlen) && (size_t)addrlen <= sizeof(addr)) { pseudo.ip6_src = addr.sin6_addr; } } @@ -282,7 +288,7 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_ /* Copy first part of original contents to ICMP message */ - memmove(packet->data + ether_size + ip6_size + icmp6_size, packet->data + ether_size, pseudo.length); + memmove(DATA(packet) + ether_size + ip6_size + icmp6_size, DATA(packet) + ether_size, pseudo.length); /* Fill in IPv6 header */ @@ -308,14 +314,14 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_ checksum = inet_checksum(&pseudo, sizeof(pseudo), ~0); checksum = inet_checksum(&icmp6, icmp6_size, checksum); - checksum = inet_checksum(packet->data + ether_size + ip6_size + icmp6_size, ntohl(pseudo.length) - icmp6_size, checksum); + checksum = inet_checksum(DATA(packet) + ether_size + ip6_size + icmp6_size, ntohl(pseudo.length) - icmp6_size, checksum); icmp6.icmp6_cksum = checksum; /* Copy structs on stack back to packet */ - memcpy(packet->data + ether_size, &ip6, ip6_size); - memcpy(packet->data + ether_size + ip6_size, &icmp6, icmp6_size); + memcpy(DATA(packet) + ether_size, &ip6, ip6_size); + memcpy(DATA(packet) + ether_size + ip6_size, &icmp6, icmp6_size); packet->len = ether_size + ip6_size + ntohl(pseudo.length); @@ -323,11 +329,11 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_ } static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) { - uint16_t type = packet->data[12] << 8 | packet->data[13]; + uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13]; length_t ethlen = ether_size; if(type == ETH_P_8021Q) { - type = packet->data[16] << 8 | packet->data[17]; + type = DATA(packet)[16] << 8 | DATA(packet)[17]; ethlen += 4; } @@ -337,27 +343,27 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) { return false; } - if(packet->data[ethlen + 8] <= 1) { - if(packet->data[ethlen + 11] != IPPROTO_ICMP || packet->data[ethlen + 32] != ICMP_TIME_EXCEEDED) { + if(DATA(packet)[ethlen + 8] <= 1) { + if(DATA(packet)[ethlen + 11] != IPPROTO_ICMP || DATA(packet)[ethlen + 32] != ICMP_TIME_EXCEEDED) { route_ipv4_unreachable(source, packet, ethlen, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL); } return false; } - uint16_t old = packet->data[ethlen + 8] << 8 | packet->data[ethlen + 9]; - packet->data[ethlen + 8]--; - uint16_t new = packet->data[ethlen + 8] << 8 | packet->data[ethlen + 9]; + uint16_t old = DATA(packet)[ethlen + 8] << 8 | DATA(packet)[ethlen + 9]; + DATA(packet)[ethlen + 8]--; + uint16_t new = DATA(packet)[ethlen + 8] << 8 | DATA(packet)[ethlen + 9]; - uint32_t checksum = packet->data[ethlen + 10] << 8 | packet->data[ethlen + 11]; + uint32_t checksum = DATA(packet)[ethlen + 10] << 8 | DATA(packet)[ethlen + 11]; checksum += old + (~new & 0xFFFF); while(checksum >> 16) { checksum = (checksum & 0xFFFF) + (checksum >> 16); } - packet->data[ethlen + 10] = checksum >> 8; - packet->data[ethlen + 11] = checksum & 0xff; + DATA(packet)[ethlen + 10] = checksum >> 8; + DATA(packet)[ethlen + 11] = checksum & 0xff; return true; @@ -366,15 +372,15 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) { return false; } - if(packet->data[ethlen + 7] <= 1) { - if(packet->data[ethlen + 6] != IPPROTO_ICMPV6 || packet->data[ethlen + 40] != ICMP6_TIME_EXCEEDED) { + if(DATA(packet)[ethlen + 7] <= 1) { + if(DATA(packet)[ethlen + 6] != IPPROTO_ICMPV6 || DATA(packet)[ethlen + 40] != ICMP6_TIME_EXCEEDED) { route_ipv6_unreachable(source, packet, ethlen, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT); } return false; } - packet->data[ethlen + 7]--; + DATA(packet)[ethlen + 7]--; return true; @@ -396,16 +402,25 @@ static void clamp_mss(const node_t *source, const node_t *via, vpn_packet_t *pac /* Find TCP header */ int start = ether_size; - uint16_t type = packet->data[12] << 8 | packet->data[13]; + uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13]; if(type == ETH_P_8021Q) { start += 4; - type = packet->data[16] << 8 | packet->data[17]; + type = DATA(packet)[16] << 8 | DATA(packet)[17]; } - if(type == ETH_P_IP && packet->data[start + 9] == 6) { - start += (packet->data[start] & 0xf) * 4; - } else if(type == ETH_P_IPV6 && packet->data[start + 6] == 6) { + /* IP in IP (RFC 2003) packet */ + if(type == ETH_P_IP && DATA(packet)[start + 9] == 4) { + start += 20; + } + + if(packet->len <= start + 20) { + return; + } + + if(type == ETH_P_IP && DATA(packet)[start + 9] == 6) { + start += (DATA(packet)[start] & 0xf) * 4; + } else if(type == ETH_P_IPV6 && DATA(packet)[start + 6] == 6) { start += 40; } else { return; @@ -416,7 +431,7 @@ static void clamp_mss(const node_t *source, const node_t *via, vpn_packet_t *pac } /* Use data offset field to calculate length of options field */ - int len = ((packet->data[start + 12] >> 4) - 5) * 4; + int len = ((DATA(packet)[start + 12] >> 4) - 5) * 4; if(packet->len < start + 20 + len) { return; @@ -424,75 +439,104 @@ static void clamp_mss(const node_t *source, const node_t *via, vpn_packet_t *pac /* Search for MSS option header */ for(int i = 0; i < len;) { - if(packet->data[start + 20 + i] == 0) { + if(DATA(packet)[start + 20 + i] == 0) { break; } - if(packet->data[start + 20 + i] == 1) { + if(DATA(packet)[start + 20 + i] == 1) { i++; continue; } - if(i > len - 2 || i > len - packet->data[start + 21 + i]) { + if(i > len - 2 || i > len - DATA(packet)[start + 21 + i]) { break; } - if(packet->data[start + 20 + i] != 2) { - if(packet->data[start + 21 + i] < 2) { + if(DATA(packet)[start + 20 + i] != 2) { + if(DATA(packet)[start + 21 + i] < 2) { break; } - i += packet->data[start + 21 + i]; + i += DATA(packet)[start + 21 + i]; continue; } - if(packet->data[start + 21] != 4) { + if(DATA(packet)[start + 21] != 4) { break; } /* Found it */ - uint16_t oldmss = packet->data[start + 22 + i] << 8 | packet->data[start + 23 + i]; + uint16_t oldmss = DATA(packet)[start + 22 + i] << 8 | DATA(packet)[start + 23 + i]; uint16_t newmss = mtu - start - 20; - uint32_t csum = packet->data[start + 16] << 8 | packet->data[start + 17]; + uint32_t csum = DATA(packet)[start + 16] << 8 | DATA(packet)[start + 17]; if(oldmss <= newmss) { break; } - ifdebug(TRAFFIC) logger(LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss); + logger(DEBUG_TRAFFIC, LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss); /* Update the MSS value and the checksum */ - packet->data[start + 22 + i] = newmss >> 8; - packet->data[start + 23 + i] = newmss & 0xff; + DATA(packet)[start + 22 + i] = newmss >> 8; + DATA(packet)[start + 23 + i] = newmss & 0xff; csum ^= 0xffff; csum += oldmss ^ 0xffff; csum += newmss; csum = (csum & 0xffff) + (csum >> 16); csum += csum >> 16; csum ^= 0xffff; - packet->data[start + 16] = csum >> 8; - packet->data[start + 17] = csum; + DATA(packet)[start + 16] = csum >> 8; + DATA(packet)[start + 17] = csum; break; } } -static void learn_mac(mac_t *address) { - subnet_t *subnet; - avl_node_t *node; - connection_t *c; +static void age_subnets(void *data) { + (void)data; + bool left = false; - subnet = lookup_subnet_mac(myself, address); + for splay_each(subnet_t, s, myself->subnet_tree) { + if(s->expires && s->expires < now.tv_sec) { + if(debug_level >= DEBUG_TRAFFIC) { + char netstr[MAXNETSTR]; + + if(net2str(netstr, sizeof(netstr), s)) { + logger(DEBUG_TRAFFIC, LOG_INFO, "Subnet %s expired", netstr); + } + } + + for list_each(connection_t, c, connection_list) + if(c->edge) { + send_del_subnet(c, s); + } + + subnet_del(myself, s); + } else { + if(s->expires) { + left = true; + } + } + } + + if(left) + timeout_set(&age_subnets_timeout, &(struct timeval) { + 10, rand() % 100000 + }); +} + +static void learn_mac(mac_t *address) { + subnet_t *subnet = lookup_subnet_mac(myself, address); /* If we don't know this MAC address yet, store it */ if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Learned new MAC address %x:%x:%x:%x:%x:%x", - address->x[0], address->x[1], address->x[2], address->x[3], - address->x[4], address->x[5]); + logger(DEBUG_TRAFFIC, LOG_INFO, "Learned new MAC address %x:%x:%x:%x:%x:%x", + address->x[0], address->x[1], address->x[2], address->x[3], + address->x[4], address->x[5]); subnet = new_subnet(); subnet->type = SUBNET_MAC; - subnet->expires = now + macexpire; + subnet->expires = now.tv_sec + macexpire; subnet->net.mac.address = *address; subnet->weight = 10; subnet_add(myself, subnet); @@ -500,48 +544,17 @@ static void learn_mac(mac_t *address) { /* And tell all other tinc daemons it's our MAC */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - - if(c->status.active) { + for list_each(connection_t, c, connection_list) + if(c->edge) { send_add_subnet(c, subnet); } - } - } - if(subnet->expires) { - subnet->expires = now + macexpire; - } -} - -void age_subnets(void) { - subnet_t *s; - connection_t *c; - avl_node_t *node, *next, *node2; - - for(node = myself->subnet_tree->head; node; node = next) { - next = node->next; - s = node->data; - - if(s->expires && s->expires <= now) { - ifdebug(TRAFFIC) { - char netstr[MAXNETSTR]; - - if(net2str(netstr, sizeof(netstr), s)) { - logger(LOG_INFO, "Subnet %s expired", netstr); - } - } - - for(node2 = connection_tree->head; node2; node2 = node2->next) { - c = node2->data; - - if(c->status.active) { - send_del_subnet(c, s); - } - } - - subnet_update(myself, s, false); - subnet_del(myself, s); + timeout_add(&age_subnets_timeout, age_subnets, NULL, &(struct timeval) { + 10, rand() % 100000 + }); + } else { + if(subnet->expires) { + subnet->expires = now.tv_sec + macexpire; } } } @@ -560,12 +573,13 @@ static void route_broadcast(node_t *source, vpn_packet_t *packet) { static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet, length_t ether_size) { struct ip ip; vpn_packet_t fragment; - int len, maxlen, todo; + int maxlen, todo; uint8_t *offset; uint16_t ip_off, origf; - memcpy(&ip, packet->data + ether_size, ip_size); + memcpy(&ip, DATA(packet) + ether_size, ip_size); fragment.priority = packet->priority; + fragment.offset = DEFAULT_PACKET_OFFSET; if(ip.ip_hl != ip_size / 4) { return; @@ -574,21 +588,21 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet, length_t et todo = ntohs(ip.ip_len) - ip_size; if(ether_size + ip_size + todo != packet->len) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo)); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo)); return; } - ifdebug(TRAFFIC) logger(LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname); - offset = packet->data + ether_size + ip_size; + offset = DATA(packet) + ether_size + ip_size; maxlen = (MAX(dest->mtu, 590) - ether_size - ip_size) & ~0x7; ip_off = ntohs(ip.ip_off); origf = ip_off & ~IP_OFFMASK; ip_off &= IP_OFFMASK; while(todo) { - len = todo > maxlen ? maxlen : todo; - memcpy(fragment.data + ether_size + ip_size, offset, len); + int len = todo > maxlen ? maxlen : todo; + memcpy(DATA(&fragment) + ether_size + ip_size, offset, len); todo -= len; offset += len; @@ -596,8 +610,8 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet, length_t et ip.ip_off = htons(ip_off | origf | (todo ? IP_MF : 0)); ip.ip_sum = 0; ip.ip_sum = inet_checksum(&ip, ip_size, ~0); - memcpy(fragment.data, packet->data, ether_size); - memcpy(fragment.data + ether_size, &ip, ip_size); + memcpy(DATA(&fragment), DATA(packet), ether_size); + memcpy(DATA(&fragment) + ether_size, &ip, ip_size); fragment.len = ether_size + ip_size + len; send_packet(dest, &fragment); @@ -606,28 +620,37 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet, length_t et } } -static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { +static void route_ipv4(node_t *source, vpn_packet_t *packet) { + if(!checklength(source, packet, ether_size + ip_size)) { + return; + } + subnet_t *subnet; node_t *via; ipv4_t dest; - memcpy(&dest, &packet->data[30], sizeof(dest)); + memcpy(&dest, &DATA(packet)[30], sizeof(dest)); subnet = lookup_subnet_ipv4(&dest); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d", - source->name, source->hostname, - dest.x[0], - dest.x[1], - dest.x[2], - dest.x[3]); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d", + source->name, source->hostname, + dest.x[0], + dest.x[1], + dest.x[2], + dest.x[3]); route_ipv4_unreachable(source, packet, ether_size, ICMP_DEST_UNREACH, ICMP_NET_UNKNOWN); return; } + if(!subnet->owner) { + route_broadcast(source, packet); + return; + } + if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -647,13 +670,13 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { } if(priorityinheritance) { - packet->priority = packet->data[15]; + packet->priority = DATA(packet)[15]; } via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via; if(via == source) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); return; } @@ -663,9 +686,9 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { } if(via && packet->len > MAX(via->mtu, 590) && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); - if(packet->data[20] & 0x40) { + if(DATA(packet)[20] & 0x40) { packet->len = MAX(via->mtu, 590); route_ipv4_unreachable(source, packet, ether_size, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED); } else { @@ -680,48 +703,48 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { send_packet(subnet->owner, packet); } -static void route_ipv4(node_t *source, vpn_packet_t *packet) { - if(!checklength(source, packet, ether_size + ip_size)) { +static void route_neighborsol(node_t *source, vpn_packet_t *packet); + +static void route_ipv6(node_t *source, vpn_packet_t *packet) { + if(!checklength(source, packet, ether_size + ip6_size)) { return; } - if(broadcast_mode && (((packet->data[30] & 0xf0) == 0xe0) || ( - packet->data[30] == 255 && - packet->data[31] == 255 && - packet->data[32] == 255 && - packet->data[33] == 255))) { - route_broadcast(source, packet); - } else { - route_ipv4_unicast(source, packet); + if(DATA(packet)[20] == IPPROTO_ICMPV6 && checklength(source, packet, ether_size + ip6_size + icmp6_size) && DATA(packet)[54] == ND_NEIGHBOR_SOLICIT) { + route_neighborsol(source, packet); + return; } -} -static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { subnet_t *subnet; node_t *via; ipv6_t dest; - memcpy(&dest, &packet->data[38], sizeof(dest)); + memcpy(&dest, &DATA(packet)[38], sizeof(dest)); subnet = lookup_subnet_ipv6(&dest); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", - source->name, source->hostname, - ntohs(dest.x[0]), - ntohs(dest.x[1]), - ntohs(dest.x[2]), - ntohs(dest.x[3]), - ntohs(dest.x[4]), - ntohs(dest.x[5]), - ntohs(dest.x[6]), - ntohs(dest.x[7])); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + source->name, source->hostname, + ntohs(dest.x[0]), + ntohs(dest.x[1]), + ntohs(dest.x[2]), + ntohs(dest.x[3]), + ntohs(dest.x[4]), + ntohs(dest.x[5]), + ntohs(dest.x[6]), + ntohs(dest.x[7])); route_ipv6_unreachable(source, packet, ether_size, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR); return; } + if(!subnet->owner) { + route_broadcast(source, packet); + return; + } + if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -735,20 +758,19 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { return; } - if(decrement_ttl && source != myself && subnet->owner != myself) { + if(decrement_ttl && source != myself && subnet->owner != myself) if(!do_decrement_ttl(source, packet)) { return; } - } if(priorityinheritance) { - packet->priority = ((packet->data[14] & 0x0f) << 4) | (packet->data[15] >> 4); + packet->priority = ((DATA(packet)[14] & 0x0f) << 4) | (DATA(packet)[15] >> 4); } via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via; if(via == source) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); return; } @@ -758,7 +780,7 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { } if(via && packet->len > MAX(via->mtu, 1294) && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); packet->len = MAX(via->mtu, 1294); route_ipv6_unreachable(source, packet, ether_size, ICMP6_PACKET_TOO_BIG, 0); return; @@ -780,8 +802,8 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { bool has_opt; struct { - struct in6_addr ip6_src; /* source address */ - struct in6_addr ip6_dst; /* destination address */ + struct in6_addr ip6_src; + struct in6_addr ip6_dst; uint32_t length; uint32_t next; } pseudo; @@ -793,30 +815,30 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { has_opt = packet->len >= ether_size + ip6_size + ns_size + opt_size + ETH_ALEN; if(source != myself) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname); return; } /* Copy headers from packet to structs on the stack */ - memcpy(&ip6, packet->data + ether_size, ip6_size); - memcpy(&ns, packet->data + ether_size + ip6_size, ns_size); + memcpy(&ip6, DATA(packet) + ether_size, ip6_size); + memcpy(&ns, DATA(packet) + ether_size + ip6_size, ns_size); if(has_opt) { - memcpy(&opt, packet->data + ether_size + ip6_size + ns_size, opt_size); + memcpy(&opt, DATA(packet) + ether_size + ip6_size + ns_size, opt_size); } /* First, snatch the source address from the neighbor solicitation packet */ if(overwrite_mac) { - memcpy(mymac.x, packet->data + ETH_ALEN, ETH_ALEN); + memcpy(mymac.x, DATA(packet) + ETH_ALEN, ETH_ALEN); } /* Check if this is a valid neighbor solicitation request */ if(ns.nd_ns_hdr.icmp6_type != ND_NEIGHBOR_SOLICIT || (has_opt && opt.nd_opt_type != ND_OPT_SOURCE_LINKADDR)) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request"); return; } @@ -840,11 +862,11 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { if(has_opt) { checksum = inet_checksum(&opt, opt_size, checksum); - checksum = inet_checksum(packet->data + ether_size + ip6_size + ns_size + opt_size, ETH_ALEN, checksum); + checksum = inet_checksum(DATA(packet) + ether_size + ip6_size + ns_size + opt_size, ETH_ALEN, checksum); } if(checksum) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request"); return; } @@ -853,15 +875,15 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv6((ipv6_t *) &ns.nd_ns_target); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", - ntohs(((uint16_t *) &ns.nd_ns_target)[0]), - ntohs(((uint16_t *) &ns.nd_ns_target)[1]), - ntohs(((uint16_t *) &ns.nd_ns_target)[2]), - ntohs(((uint16_t *) &ns.nd_ns_target)[3]), - ntohs(((uint16_t *) &ns.nd_ns_target)[4]), - ntohs(((uint16_t *) &ns.nd_ns_target)[5]), - ntohs(((uint16_t *) &ns.nd_ns_target)[6]), - ntohs(((uint16_t *) &ns.nd_ns_target)[7])); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + ntohs(((uint16_t *) &ns.nd_ns_target)[0]), + ntohs(((uint16_t *) &ns.nd_ns_target)[1]), + ntohs(((uint16_t *) &ns.nd_ns_target)[2]), + ntohs(((uint16_t *) &ns.nd_ns_target)[3]), + ntohs(((uint16_t *) &ns.nd_ns_target)[4]), + ntohs(((uint16_t *) &ns.nd_ns_target)[5]), + ntohs(((uint16_t *) &ns.nd_ns_target)[6]), + ntohs(((uint16_t *) &ns.nd_ns_target)[7])); return; } @@ -879,19 +901,19 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { /* Create neighbor advertation reply */ - memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ - packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ + memcpy(DATA(packet), DATA(packet) + ETH_ALEN, ETH_ALEN); /* copy destination address */ + DATA(packet)[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ - ip6.ip6_dst = ip6.ip6_src; /* swap destination and source protocol address */ + ip6.ip6_dst = ip6.ip6_src; /* swap destination and source protocol address */ ip6.ip6_src = ns.nd_ns_target; if(has_opt) { - memcpy(packet->data + ether_size + ip6_size + ns_size + opt_size, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ + memcpy(DATA(packet) + ether_size + ip6_size + ns_size + opt_size, DATA(packet) + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ } ns.nd_ns_cksum = 0; ns.nd_ns_type = ND_NEIGHBOR_ADVERT; - ns.nd_ns_reserved = htonl(0x40000000UL); /* Set solicited flag */ + ns.nd_ns_reserved = htonl(0x40000000UL); /* Set solicited flag */ opt.nd_opt_type = ND_OPT_TARGET_LINKADDR; /* Create pseudo header */ @@ -914,40 +936,23 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { if(has_opt) { checksum = inet_checksum(&opt, opt_size, checksum); - checksum = inet_checksum(packet->data + ether_size + ip6_size + ns_size + opt_size, ETH_ALEN, checksum); + checksum = inet_checksum(DATA(packet) + ether_size + ip6_size + ns_size + opt_size, ETH_ALEN, checksum); } ns.nd_ns_hdr.icmp6_cksum = checksum; /* Copy structs on stack back to packet */ - memcpy(packet->data + ether_size, &ip6, ip6_size); - memcpy(packet->data + ether_size + ip6_size, &ns, ns_size); + memcpy(DATA(packet) + ether_size, &ip6, ip6_size); + memcpy(DATA(packet) + ether_size + ip6_size, &ns, ns_size); if(has_opt) { - memcpy(packet->data + ether_size + ip6_size + ns_size, &opt, opt_size); + memcpy(DATA(packet) + ether_size + ip6_size + ns_size, &opt, opt_size); } send_packet(source, packet); } -static void route_ipv6(node_t *source, vpn_packet_t *packet) { - if(!checklength(source, packet, ether_size + ip6_size)) { - return; - } - - if(packet->data[20] == IPPROTO_ICMPV6 && checklength(source, packet, ether_size + ip6_size + icmp6_size) && packet->data[54] == ND_NEIGHBOR_SOLICIT) { - route_neighborsol(source, packet); - return; - } - - if(broadcast_mode && packet->data[38] == 255) { - route_broadcast(source, packet); - } else { - route_ipv6_unicast(source, packet); - } -} - /* RFC 826 */ static void route_arp(node_t *source, vpn_packet_t *packet) { @@ -960,25 +965,25 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { } if(source != myself) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname); return; } /* First, snatch the source address from the ARP packet */ if(overwrite_mac) { - memcpy(mymac.x, packet->data + ETH_ALEN, ETH_ALEN); + memcpy(mymac.x, DATA(packet) + ETH_ALEN, ETH_ALEN); } /* Copy headers from packet to structs on the stack */ - memcpy(&arp, packet->data + ether_size, arp_size); + memcpy(&arp, DATA(packet) + ether_size, arp_size); /* Check if this is a valid ARP request */ if(ntohs(arp.arp_hrd) != ARPHRD_ETHER || ntohs(arp.arp_pro) != ETH_P_IP || arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof(addr) || ntohs(arp.arp_op) != ARPOP_REQUEST) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type ARP request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type ARP request"); return; } @@ -987,9 +992,9 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv4((ipv4_t *) &arp.arp_tpa); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d", - arp.arp_tpa[0], arp.arp_tpa[1], arp.arp_tpa[2], - arp.arp_tpa[3]); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d", + arp.arp_tpa[0], arp.arp_tpa[1], arp.arp_tpa[2], + arp.arp_tpa[3]); return; } @@ -1004,20 +1009,18 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { return; } - memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ - packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ + memcpy(&addr, arp.arp_tpa, sizeof(addr)); /* save protocol addr */ + memcpy(arp.arp_tpa, arp.arp_spa, sizeof(addr)); /* swap destination and source protocol address */ + memcpy(arp.arp_spa, &addr, sizeof(addr)); /* ... */ - memcpy(&addr, arp.arp_tpa, sizeof(addr)); /* save protocol addr */ - memcpy(arp.arp_tpa, arp.arp_spa, sizeof(addr)); /* swap destination and source protocol address */ - memcpy(arp.arp_spa, &addr, sizeof(addr)); /* ... */ - - memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN); /* set target hard/proto addr */ - memcpy(arp.arp_sha, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ + memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN); /* set target hard/proto addr */ + memcpy(arp.arp_sha, DATA(packet) + ETH_ALEN, ETH_ALEN); /* set source hard/proto addr */ + arp.arp_sha[ETH_ALEN - 1] ^= 0xFF; /* for consistency with route_packet() */ arp.arp_op = htons(ARPOP_REPLY); /* Copy structs on stack back to packet */ - memcpy(packet->data + ether_size, &arp, arp_size); + memcpy(DATA(packet) + ether_size, &arp, arp_size); send_packet(source, packet); } @@ -1030,22 +1033,22 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { if(source == myself) { mac_t src; - memcpy(&src, &packet->data[6], sizeof(src)); + memcpy(&src, &DATA(packet)[6], sizeof(src)); learn_mac(&src); } /* Lookup destination address */ - memcpy(&dest, &packet->data[0], sizeof(dest)); + memcpy(&dest, &DATA(packet)[0], sizeof(dest)); subnet = lookup_subnet_mac(NULL, &dest); - if(!subnet) { + if(!subnet || !subnet->owner) { route_broadcast(source, packet); return; } if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -1058,13 +1061,13 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { return; } - uint16_t type = packet->data[12] << 8 | packet->data[13]; + uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13]; if(priorityinheritance) { if(type == ETH_P_IP && packet->len >= ether_size + ip_size) { - packet->priority = packet->data[15]; + packet->priority = DATA(packet)[15]; } else if(type == ETH_P_IPV6 && packet->len >= ether_size + ip6_size) { - packet->priority = ((packet->data[14] & 0x0f) << 4) | (packet->data[15] >> 4); + packet->priority = ((DATA(packet)[14] & 0x0f) << 4) | (DATA(packet)[15] >> 4); } } @@ -1077,16 +1080,16 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { } if(via && packet->len > via->mtu && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); length_t ethlen = 14; if(type == ETH_P_8021Q) { - type = packet->data[16] << 8 | packet->data[17]; + type = DATA(packet)[16] << 8 | DATA(packet)[17]; ethlen += 4; } if(type == ETH_P_IP && packet->len > 576 + ethlen) { - if(packet->data[6 + ethlen] & 0x40) { + if(DATA(packet)[6 + ethlen] & 0x40) { packet->len = via->mtu; route_ipv4_unreachable(source, packet, ethlen, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED); } else { @@ -1106,7 +1109,32 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { send_packet(subnet->owner, packet); } +static void send_pcap(vpn_packet_t *packet) { + pcap = false; + + for list_each(connection_t, c, connection_list) { + if(!c->status.pcap) { + continue; + } + + pcap = true; + int len = packet->len; + + if(c->outmaclength && c->outmaclength < len) { + len = c->outmaclength; + } + + if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, len)) { + send_meta(c, (char *)DATA(packet), len); + } + } +} + void route(node_t *source, vpn_packet_t *packet) { + if(pcap) { + send_pcap(packet); + } + if(forwarding_mode == FMODE_KERNEL && source != myself) { send_packet(myself, packet); return; @@ -1116,10 +1144,10 @@ void route(node_t *source, vpn_packet_t *packet) { return; } - switch(routing_mode) { - case RMODE_ROUTER: { - uint16_t type = packet->data[12] << 8 | packet->data[13]; + uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13]; + switch(routing_mode) { + case RMODE_ROUTER: switch(type) { case ETH_P_ARP: route_arp(source, packet); @@ -1134,11 +1162,11 @@ void route(node_t *source, vpn_packet_t *packet) { break; default: - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type); break; } - } - break; + + break; case RMODE_SWITCH: route_mac(source, packet); diff --git a/src/route.h b/src/route.h index 5b9e808..095448a 100644 --- a/src/route.h +++ b/src/route.h @@ -50,10 +50,10 @@ extern bool directonly; extern bool overwrite_mac; extern bool priorityinheritance; extern int macexpire; +extern bool pcap; extern mac_t mymac; -extern void age_subnets(void); extern void route(struct node_t *source, struct vpn_packet_t *packet); #endif diff --git a/src/rsa.h b/src/rsa.h new file mode 100644 index 0000000..a31ec24 --- /dev/null +++ b/src/rsa.h @@ -0,0 +1,36 @@ +#ifndef TINC_RSA_H +#define TINC_RSA_H + +/* + rsa.h -- RSA key handling + Copyright (C) 2007-2013 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. +*/ + +#ifndef TINC_RSA_INTERNAL +typedef struct rsa rsa_t; +#endif + +extern void rsa_free(rsa_t *rsa); +extern rsa_t *rsa_set_hex_public_key(char *n, char *e) __attribute__((__malloc__)); +extern rsa_t *rsa_set_hex_private_key(char *n, char *e, char *d) __attribute__((__malloc__)); +extern rsa_t *rsa_read_pem_public_key(FILE *fp) __attribute__((__malloc__)); +extern rsa_t *rsa_read_pem_private_key(FILE *fp) __attribute__((__malloc__)); +extern size_t rsa_size(rsa_t *rsa); +extern bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) __attribute__((__warn_unused_result__)); +extern bool rsa_private_decrypt(rsa_t *rsa, void *in, size_t len, void *out) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/rsagen.h b/src/rsagen.h new file mode 100644 index 0000000..a661607 --- /dev/null +++ b/src/rsagen.h @@ -0,0 +1,29 @@ +#ifndef TINC_RSAGEN_H +#define TINC_RSAGEN_H + +/* + rsagen.h -- RSA key generation and export + Copyright (C) 2008-2013 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 "rsa.h" + +extern rsa_t *rsa_generate(size_t bits, unsigned long exponent) __attribute__((__malloc__)); +extern bool rsa_write_pem_public_key(rsa_t *rsa, FILE *fp) __attribute__((__warn_unused_result__)); +extern bool rsa_write_pem_private_key(rsa_t *rsa, FILE *fp) __attribute__((__warn_unused_result__)); + +#endif diff --git a/src/script.c b/src/script.c new file mode 100644 index 0000000..e465ab7 --- /dev/null +++ b/src/script.c @@ -0,0 +1,237 @@ +/* + script.c -- call an external script + Copyright (C) 1999-2005 Ivo Timmermans, + 2000-2018 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 "conf.h" +#include "device.h" +#include "logger.h" +#include "names.h" +#include "script.h" +#include "xalloc.h" + +#ifdef HAVE_PUTENV +static void unputenv(const char *p) { + const char *e = strchr(p, '='); + + if(!e) { + return; + } + + int len = e - p; +#ifndef HAVE_UNSETENV +#ifdef HAVE_MINGW + // Windows requires putenv("FOO=") to unset %FOO% + len++; +#endif +#endif + char var[len + 1]; + strncpy(var, p, len); + var[len] = 0; +#ifdef HAVE_UNSETENV + unsetenv(var); +#else + // We must keep what we putenv() around in memory. + // To do this without memory leaks, keep things in a list and reuse if possible. + static list_t list = {0}; + + for list_each(char, data, &list) { + if(!strcmp(data, var)) { + putenv(data); + return; + } + } + + char *data = xstrdup(var); + list_insert_tail(&list, data); + putenv(data); +#endif +} +#else +static void putenv(const char *p) {} +static void unputenv(const char *p) {} +#endif + +static const int min_env_size = 10; + +int environment_add(environment_t *env, const char *format, ...) { + if(env->n >= env->size) { + env->size = env->n ? env->n * 2 : min_env_size; + env->entries = xrealloc(env->entries, env->size * sizeof(*env->entries)); + } + + if(format) { + va_list ap; + va_start(ap, format); + vasprintf(&env->entries[env->n], format, ap); + va_end(ap); + } else { + env->entries[env->n] = NULL; + } + + return env->n++; +} + +void environment_update(environment_t *env, int pos, const char *format, ...) { + free(env->entries[pos]); + va_list ap; + va_start(ap, format); + vasprintf(&env->entries[pos], format, ap); + va_end(ap); +} + +void environment_init(environment_t *env) { + env->n = 0; + env->size = min_env_size; + env->entries = xzalloc(env->size * sizeof(*env->entries)); + + if(netname) { + environment_add(env, "NETNAME=%s", netname); + } + + if(myname) { + environment_add(env, "NAME=%s", myname); + } + + if(device) { + environment_add(env, "DEVICE=%s", device); + } + + if(iface) { + environment_add(env, "INTERFACE=%s", iface); + } + + if(debug_level >= 0) { + environment_add(env, "DEBUG=%d", debug_level); + } +} + +void environment_exit(environment_t *env) { + for(int i = 0; i < env->n; i++) { + free(env->entries[i]); + } + + free(env->entries); +} + +bool execute_script(const char *name, environment_t *env) { + char scriptname[PATH_MAX]; + char *command; + + snprintf(scriptname, sizeof(scriptname), "%s" SLASH "%s%s", confbase, name, scriptextension); + + /* First check if there is a script */ + +#ifdef HAVE_MINGW + + if(!*scriptextension) { + const char *pathext = getenv("PATHEXT"); + + if(!pathext) { + pathext = ".COM;.EXE;.BAT;.CMD"; + } + + size_t pathlen = strlen(pathext); + size_t scriptlen = strlen(scriptname); + char fullname[scriptlen + pathlen + 1]; + char *ext = fullname + scriptlen; + strncpy(fullname, scriptname, sizeof(fullname)); + + const char *p = pathext; + bool found = false; + + while(p && *p) { + const char *q = strchr(p, ';'); + + if(q) { + memcpy(ext, p, q - p); + ext[q - p] = 0; + q++; + } else { + strncpy(ext, p, pathlen + 1); + } + + if((found = !access(fullname, F_OK))) { + break; + } + + p = q; + } + + if(!found) { + return true; + } + } else +#endif + + if(access(scriptname, F_OK)) { + return true; + } + + logger(DEBUG_STATUS, LOG_INFO, "Executing script %s", name); + + /* Set environment */ + + for(int i = 0; i < env->n; i++) { + putenv(env->entries[i]); + } + + if(scriptinterpreter) { + xasprintf(&command, "%s \"%s\"", scriptinterpreter, scriptname); + } else { + xasprintf(&command, "\"%s\"", scriptname); + } + + int status = system(command); + + free(command); + + /* Unset environment */ + + for(int i = 0; i < env->n; i++) { + unputenv(env->entries[i]); + } + + if(status != -1) { +#ifdef WEXITSTATUS + + if(WIFEXITED(status)) { /* Child exited by itself */ + if(WEXITSTATUS(status)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s exited with non-zero status %d", + name, WEXITSTATUS(status)); + return false; + } + } else if(WIFSIGNALED(status)) { /* Child was killed by a signal */ + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s was killed by signal %d (%s)", + name, WTERMSIG(status), strsignal(WTERMSIG(status))); + return false; + } else { /* Something strange happened */ + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s terminated abnormally", name); + return false; + } + +#endif + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "system", strerror(errno)); + return false; + } + + return true; +} diff --git a/src/script.h b/src/script.h new file mode 100644 index 0000000..5172034 --- /dev/null +++ b/src/script.h @@ -0,0 +1,38 @@ +#ifndef TINC_SCRIPT_H +#define TINC_SCRIPT_H + +/* + script.h -- header file for script.c + Copyright (C) 1999-2005 Ivo Timmermans, + 2000-2017 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. +*/ + +typedef struct environment { + int n; + int size; + char **entries; +} environment_t; + +extern int environment_add(environment_t *env, const char *format, ...); +extern int environment_placeholder(environment_t *env); +extern void environment_update(environment_t *env, int pos, const char *format, ...); +extern void environment_init(environment_t *env); +extern void environment_exit(environment_t *env); + +extern bool execute_script(const char *name, environment_t *env); + +#endif diff --git a/src/solaris/device.c b/src/solaris/device.c index fa2e6e6..f27954b 100644 --- a/src/solaris/device.c +++ b/src/solaris/device.c @@ -2,7 +2,7 @@ device.c -- Interaction with Solaris tun device Copyright (C) 2001-2005 Ivo Timmermans, 2002-2010 OpenVPN Technologies, Inc. - 2001-2017 Guus Sliepen + 2001-2014 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 @@ -29,6 +29,7 @@ #include "../conf.h" #include "../device.h" #include "../logger.h" +#include "../names.h" #include "../net.h" #include "../route.h" #include "../utils.h" @@ -49,15 +50,11 @@ static enum { } device_type = DEVICE_TYPE_TUN; int device_fd = -1; -static int if_fd = -1; static int ip_fd = -1; char *device = NULL; char *iface = NULL; static const char *device_info = NULL; -uint64_t device_total_in = 0; -uint64_t device_total_out = 0; - static bool setup_device(void) { char *type; @@ -75,7 +72,7 @@ static bool setup_device(void) { else if(!strcasecmp(type, "tap")) { device_type = DEVICE_TYPE_TAP; } else { - logger(LOG_ERR, "Unknown device type %s!", type); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown device type %s!", type); return false; } } else { @@ -90,19 +87,15 @@ static bool setup_device(void) { device_info = "Solaris tap device"; } - if(device_type == DEVICE_TYPE_TAP && routing_mode == RMODE_ROUTER) { - overwrite_mac = true; - } - /* The following is black magic copied from OpenVPN. */ if((ip_fd = open(IP_DEVICE, O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open %s: %s\n", IP_DEVICE, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", IP_DEVICE, strerror(errno)); return false; } if((device_fd = open(device, O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); return false; } @@ -141,23 +134,25 @@ static bool setup_device(void) { } if(!found) { - logger(LOG_ERR, "Could not find free PPA for %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not find free PPA for %s %s!", device_info, device); return false; } } else { /* try this particular one */ if((ppa = ioctl(device_fd, I_STR, &strioc_ppa)) < 0) { - logger(LOG_ERR, "Could not assign PPA %d for %s %s!", ppa, device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not assign PPA %d for %s %s!", ppa, device_info, device); return false; } } + int if_fd; + if((if_fd = open(device, O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); return false; } if(ioctl(if_fd, I_PUSH, "ip") < 0) { - logger(LOG_ERR, "Could not push IP module onto %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not push IP module onto %s %s!", device_info, device); return false; } @@ -179,7 +174,7 @@ static bool setup_device(void) { if(device_type == DEVICE_TYPE_TUN) { /* Assign ppa according to the unit number returned by tun device */ if(ioctl(if_fd, IF_UNITSEL, (char *)&ppa) < 0) { - logger(LOG_ERR, "Could not set PPA %d on %s %s!", ppa, device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set PPA %d on %s %s!", ppa, device_info, device); return false; } } @@ -190,7 +185,7 @@ static bool setup_device(void) { struct lifreq ifr = {}; if(ioctl(if_fd, SIOCGLIFFLAGS, &ifr) < 0) { - logger(LOG_ERR, "Could not set flags on %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set flags on %s %s!", device_info, device); return false; } @@ -199,18 +194,18 @@ static bool setup_device(void) { /* Assign ppa according to the unit number returned by tun device */ if(ioctl(if_fd, SIOCSLIFNAME, &ifr) < 0) { - logger(LOG_ERR, "Could not set PPA %d on %s %s!", ppa, device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set PPA %d on %s %s!", ppa, device_info, device); return false; } if(ioctl(if_fd, SIOCGLIFFLAGS, &ifr) < 0) { - logger(LOG_ERR, "Could not set flags on %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set flags on %s %s!", device_info, device); return false; } /* Push arp module to if_fd */ if(ioctl(if_fd, I_PUSH, "arp") < 0) { - logger(LOG_ERR, "Could not push ARP module onto %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not push ARP module onto %s %s!", device_info, device); return false; } @@ -223,19 +218,19 @@ static bool setup_device(void) { /* Push arp module to ip_fd */ if(ioctl(ip_fd, I_PUSH, "arp") < 0) { - logger(LOG_ERR, "Could not push ARP module onto %s!", IP_DEVICE); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not push ARP module onto %s!", IP_DEVICE); return false; } /* Open arp_fd */ if((arp_fd = open(device, O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s\n", device, strerror(errno)); return false; } /* Push arp module to arp_fd */ if(ioctl(arp_fd, I_PUSH, "arp") < 0) { - logger(LOG_ERR, "Could not push ARP module onto %s %s!", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not push ARP module onto %s %s!", device_info, device); return false; } @@ -247,7 +242,7 @@ static bool setup_device(void) { }; if(ioctl(arp_fd, I_STR, &strioc_if) < 0) { - logger(LOG_ERR, "Could not set ifname to %s %s", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set ifname to %s %s", device_info, device); return false; } } @@ -255,13 +250,13 @@ static bool setup_device(void) { int ip_muxid, arp_muxid; if((ip_muxid = ioctl(ip_fd, I_PLINK, if_fd)) < 0) { - logger(LOG_ERR, "Could not link %s %s to IP", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not link %s %s to IP", device_info, device); return false; } if(device_type == DEVICE_TYPE_TAP) { if((arp_muxid = ioctl(ip_fd, I_PLINK, arp_fd)) < 0) { - logger(LOG_ERR, "Could not link %s %s to ARP", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not link %s %s to ARP", device_info, device); return false; } @@ -284,7 +279,7 @@ static bool setup_device(void) { } ioctl(ip_fd, I_PUNLINK, ip_muxid); - logger(LOG_ERR, "Could not set multiplexor id for %s %s", device_info, device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not set multiplexor id for %s %s", device_info, device); return false; } @@ -295,7 +290,7 @@ static bool setup_device(void) { fcntl(ip_fd, F_SETFD, FD_CLOEXEC); #endif - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } @@ -314,10 +309,14 @@ static void close_device(void) { } close(ip_fd); + ip_fd = -1; close(device_fd); + device_fd = -1; free(device); + device = NULL; free(iface); + iface = NULL; } static bool read_packet(vpn_packet_t *packet) { @@ -328,36 +327,36 @@ static bool read_packet(vpn_packet_t *packet) { switch(device_type) { case DEVICE_TYPE_TUN: sbuf.maxlen = MTU - 14; - sbuf.buf = (char *)packet->data + 14; + sbuf.buf = (char *)DATA(packet) + 14; if((result = getmsg(device_fd, NULL, &sbuf, &f)) < 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - switch(packet->data[14] >> 4) { + switch(DATA(packet)[14] >> 4) { case 4: - packet->data[12] = 0x08; - packet->data[13] = 0x00; + DATA(packet)[12] = 0x08; + DATA(packet)[13] = 0x00; break; case 6: - packet->data[12] = 0x86; - packet->data[13] = 0xDD; + DATA(packet)[12] = 0x86; + DATA(packet)[13] = 0xDD; break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, "Unknown IP version %d while reading packet from %s %s", packet->data[14] >> 4, device_info, device); + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown IP version %d while reading packet from %s %s", DATA(packet)[14] >> 4, device_info, device); return false; } - memset(packet->data, 0, 12); + memset(DATA(packet), 0, 12); packet->len = sbuf.len + 14; break; case DEVICE_TYPE_TAP: sbuf.maxlen = MTU; - sbuf.buf = (char *)packet->data; + sbuf.buf = (char *)DATA(packet); if((result = getmsg(device_fd, NULL, &sbuf, &f)) < 0) { logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); @@ -371,22 +370,20 @@ static bool read_packet(vpn_packet_t *packet) { abort(); } - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); struct strbuf sbuf; switch(device_type) { case DEVICE_TYPE_TUN: sbuf.len = packet->len - 14; - sbuf.buf = (char *)packet->data + 14; + sbuf.buf = (char *)DATA(packet) + 14; if(putmsg(device_fd, NULL, &sbuf, 0) < 0) { logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); @@ -397,7 +394,7 @@ static bool write_packet(vpn_packet_t *packet) { case DEVICE_TYPE_TAP: sbuf.len = packet->len; - sbuf.buf = (char *)packet->data; + sbuf.buf = (char *)DATA(packet); if(putmsg(device_fd, NULL, &sbuf, 0) < 0) { logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); @@ -410,21 +407,12 @@ static bool write_packet(vpn_packet_t *packet) { abort(); } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t os_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/splay_tree.c b/src/splay_tree.c new file mode 100644 index 0000000..4d97373 --- /dev/null +++ b/src/splay_tree.c @@ -0,0 +1,630 @@ +/* + splay_tree.c -- splay tree and linked list convenience + Copyright (C) 2004-2013 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 "splay_tree.h" +#include "xalloc.h" + +/* Splay operation */ + +static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *result) { + splay_node_t left = {0}, right = {0}; + splay_node_t *leftbottom = &left, *rightbottom = &right, *child, *grandchild; + splay_node_t *root = tree->root; + int c; + + if(!root) { + if(result) { + *result = 0; + } + + return NULL; + } + + while((c = tree->compare(data, root->data))) { + if(c < 0 && (child = root->left)) { + c = tree->compare(data, child->data); + + if(c < 0 && (grandchild = child->left)) { + rightbottom->left = child; + child->parent = rightbottom; + rightbottom = child; + + if((root->left = child->right)) { + child->right->parent = root; + } + + child->right = root; + root->parent = child; + + child->left = NULL; + grandchild->parent = NULL; + + root = grandchild; + } else if(c > 0 && (grandchild = child->right)) { + leftbottom->right = child; + child->parent = leftbottom; + leftbottom = child; + + child->right = NULL; + grandchild->parent = NULL; + + rightbottom->left = root; + root->parent = rightbottom; + rightbottom = root; + + root->left = NULL; + + root = grandchild; + } else { + rightbottom->left = root; + root->parent = rightbottom; + rightbottom = root; + + root->left = NULL; + child->parent = NULL; + + root = child; + break; + } + } else if(c > 0 && (child = root->right)) { + c = tree->compare(data, child->data); + + if(c > 0 && (grandchild = child->right)) { + leftbottom->right = child; + child->parent = leftbottom; + leftbottom = child; + + if((root->right = child->left)) { + child->left->parent = root; + } + + child->left = root; + root->parent = child; + + child->right = NULL; + grandchild->parent = NULL; + + root = grandchild; + } else if(c < 0 && (grandchild = child->left)) { + rightbottom->left = child; + child->parent = rightbottom; + rightbottom = child; + + child->left = NULL; + grandchild->parent = NULL; + + leftbottom->right = root; + root->parent = leftbottom; + leftbottom = root; + + root->right = NULL; + + root = grandchild; + } else { + leftbottom->right = root; + root->parent = leftbottom; + leftbottom = root; + + root->right = NULL; + child->parent = NULL; + + root = child; + break; + } + } else { + break; + } + } + + /* Merge trees */ + + if(left.right) { + if(root->left) { + leftbottom->right = root->left; + root->left->parent = leftbottom; + } + + root->left = left.right; + left.right->parent = root; + } + + if(right.left) { + if(root->right) { + rightbottom->left = root->right; + root->right->parent = rightbottom; + } + + root->right = right.left; + right.left->parent = root; + } + + /* Return result */ + + tree->root = root; + + if(result) { + *result = c; + } + + return tree->root; +} + +static void splay_bottom_up(splay_tree_t *tree, splay_node_t *node) { + splay_node_t *parent, *grandparent, *greatgrandparent; + + while((parent = node->parent)) { + if(!(grandparent = parent->parent)) { /* zig */ + if(node == parent->left) { + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + } else { + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + } + + parent->parent = node; + node->parent = NULL; + } else { + greatgrandparent = grandparent->parent; + + if(node == parent->left && parent == grandparent->left) { /* left zig-zig */ + if((grandparent->left = parent->right)) { + grandparent->left->parent = grandparent; + } + + parent->right = grandparent; + grandparent->parent = parent; + + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + parent->parent = node; + } else if(node == parent->right && parent == grandparent->right) { /* right zig-zig */ + if((grandparent->right = parent->left)) { + grandparent->right->parent = grandparent; + } + + parent->left = grandparent; + grandparent->parent = parent; + + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + parent->parent = node; + } else if(node == parent->right && parent == grandparent->left) { /* left-right zig-zag */ + if((parent->right = node->left)) { + parent->right->parent = parent; + } + + node->left = parent; + parent->parent = node; + + if((grandparent->left = node->right)) { + grandparent->left->parent = grandparent; + } + + node->right = grandparent; + grandparent->parent = node; + } else { /* right-left zig-zag */ + if((parent->left = node->right)) { + parent->left->parent = parent; + } + + node->right = parent; + parent->parent = node; + + if((grandparent->right = node->left)) { + grandparent->right->parent = grandparent; + } + + node->left = grandparent; + grandparent->parent = node; + } + + if((node->parent = greatgrandparent)) { + if(grandparent == greatgrandparent->left) { + greatgrandparent->left = node; + } else { + greatgrandparent->right = node; + } + } + } + } + + tree->root = node; +} + +/* (De)constructors */ + +splay_tree_t *splay_alloc_tree(splay_compare_t compare, splay_action_t delete) { + splay_tree_t *tree; + + tree = xzalloc(sizeof(splay_tree_t)); + tree->compare = compare; + tree->delete = delete; + + return tree; +} + +void splay_free_tree(splay_tree_t *tree) { + free(tree); +} + +splay_node_t *splay_alloc_node(void) { + return xzalloc(sizeof(splay_node_t)); +} + +void splay_free_node(splay_tree_t *tree, splay_node_t *node) { + if(node->data && tree->delete) { + tree->delete(node->data); + } + + free(node); +} + +/* Searching */ + +void *splay_search(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + return node ? node->data : NULL; +} + +void *splay_search_closest(splay_tree_t *tree, const void *data, int *result) { + splay_node_t *node; + + node = splay_search_closest_node(tree, data, result); + + return node ? node->data : NULL; +} + +void *splay_search_closest_smaller(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_closest_smaller_node(tree, data); + + return node ? node->data : NULL; +} + +void *splay_search_closest_greater(splay_tree_t *tree, const void *data) { + splay_node_t *node; + + node = splay_search_closest_greater_node(tree, data); + + return node ? node->data : NULL; +} + +splay_node_t *splay_search_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + return result ? NULL : node; +} + +splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *tree, const void *data, int *result) { + splay_node_t *node; + int c; + + node = tree->root; + + if(!node) { + if(result) { + *result = 0; + } + + return NULL; + } + + for(;;) { + c = tree->compare(data, node->data); + + if(c < 0) { + if(node->left) { + node = node->left; + } else { + break; + } + } else if(c > 0) { + if(node->right) { + node = node->right; + } else { + break; + } + } else { + break; + } + } + + if(result) { + *result = c; + } + + return node; +} + +splay_node_t *splay_search_closest_node(splay_tree_t *tree, const void *data, int *result) { + return splay_top_down(tree, data, result); +} + +splay_node_t *splay_search_closest_smaller_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + if(result < 0) { + node = node->prev; + } + + return node; +} + +splay_node_t *splay_search_closest_greater_node(splay_tree_t *tree, const void *data) { + splay_node_t *node; + int result; + + node = splay_search_closest_node(tree, data, &result); + + if(result > 0) { + node = node->next; + } + + return node; +} + +/* Insertion and deletion */ + +splay_node_t *splay_insert(splay_tree_t *tree, void *data) { + splay_node_t *closest, *new; + int result; + + if(!tree->root) { + new = splay_alloc_node(); + new->data = data; + splay_insert_top(tree, new); + } else { + closest = splay_search_closest_node(tree, data, &result); + + if(!result) { + return NULL; + } + + new = splay_alloc_node(); + new->data = data; + + if(result < 0) { + splay_insert_before(tree, closest, new); + } else { + splay_insert_after(tree, closest, new); + } + } + + return new; +} + +splay_node_t *splay_insert_node(splay_tree_t *tree, splay_node_t *node) { + splay_node_t *closest; + int result; + + node->left = node->right = node->parent = node->next = node->prev = NULL; + + if(!tree->root) { + splay_insert_top(tree, node); + } else { + closest = splay_search_closest_node(tree, node->data, &result); + + if(!result) { + return NULL; + } + + if(result < 0) { + splay_insert_before(tree, closest, node); + } else { + splay_insert_after(tree, closest, node); + } + } + + return node; +} + +void splay_insert_top(splay_tree_t *tree, splay_node_t *node) { + node->prev = node->next = node->left = node->right = node->parent = NULL; + tree->head = tree->tail = tree->root = node; + tree->count++; + tree->generation++; +} + +void splay_insert_before(splay_tree_t *tree, splay_node_t *before, splay_node_t *node) { + if(!before) { + if(tree->tail) { + splay_insert_after(tree, tree->tail, node); + } else { + splay_insert_top(tree, node); + } + + return; + } + + node->next = before; + + if((node->prev = before->prev)) { + before->prev->next = node; + } else { + tree->head = node; + } + + before->prev = node; + + splay_bottom_up(tree, before); + + node->right = before; + before->parent = node; + + if((node->left = before->left)) { + before->left->parent = node; + } + + before->left = NULL; + + node->parent = NULL; + tree->root = node; + tree->count++; + tree->generation++; +} + +void splay_insert_after(splay_tree_t *tree, splay_node_t *after, splay_node_t *node) { + if(!after) { + if(tree->head) { + splay_insert_before(tree, tree->head, node); + } else { + splay_insert_top(tree, node); + } + + return; + } + + node->prev = after; + + if((node->next = after->next)) { + after->next->prev = node; + } else { + tree->tail = node; + } + + after->next = node; + + splay_bottom_up(tree, after); + + node->left = after; + after->parent = node; + + if((node->right = after->right)) { + after->right->parent = node; + } + + after->right = NULL; + + node->parent = NULL; + tree->root = node; + tree->count++; + tree->generation++; +} + +splay_node_t *splay_unlink(splay_tree_t *tree, void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + if(node) { + splay_unlink_node(tree, node); + } + + return node; +} + +void splay_unlink_node(splay_tree_t *tree, splay_node_t *node) { + if(node->prev) { + node->prev->next = node->next; + } else { + tree->head = node->next; + } + + if(node->next) { + node->next->prev = node->prev; + } else { + tree->tail = node->prev; + } + + splay_bottom_up(tree, node); + + if(node->prev) { + node->left->parent = NULL; + tree->root = node->left; + + if((node->prev->right = node->right)) { + node->right->parent = node->prev; + } + } else if(node->next) { + tree->root = node->right; + node->right->parent = NULL; + } else { + tree->root = NULL; + } + + tree->count--; + tree->generation++; +} + +void splay_delete_node(splay_tree_t *tree, splay_node_t *node) { + splay_unlink_node(tree, node); + splay_free_node(tree, node); +} + +void splay_delete(splay_tree_t *tree, void *data) { + splay_node_t *node; + + node = splay_search_node(tree, data); + + if(node) { + splay_delete_node(tree, node); + } +} + +/* Fast tree cleanup */ + +void splay_delete_tree(splay_tree_t *tree) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + splay_free_node(tree, node); + } + + splay_free_tree(tree); +} + +/* Tree walking */ + +void splay_foreach(const splay_tree_t *tree, splay_action_t action) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + action(node->data); + } +} + +void splay_foreach_node(const splay_tree_t *tree, splay_action_t action) { + for(splay_node_t *node = tree->head, *next; node; node = next) { + next = node->next; + action(node); + } +} diff --git a/src/splay_tree.h b/src/splay_tree.h new file mode 100644 index 0000000..d5ab742 --- /dev/null +++ b/src/splay_tree.h @@ -0,0 +1,117 @@ +#ifndef TINC_SPLAY_TREE_H +#define TINC_SPLAY_TREE_H + +/* + splay_tree.h -- header file for splay_tree.c + Copyright (C) 2004-2013 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. +*/ + +typedef struct splay_node_t { + + /* Linked list part */ + + struct splay_node_t *next; + struct splay_node_t *prev; + + /* Tree part */ + + struct splay_node_t *parent; + struct splay_node_t *left; + struct splay_node_t *right; + + /* Payload */ + + void *data; + +} splay_node_t; + +typedef int (*splay_compare_t)(const void *data1, const void *data2); +typedef void (*splay_action_t)(const void *data); +typedef void (*splay_action_node_t)(const splay_node_t *node); + +typedef struct splay_tree_t { + + /* Linked list part */ + + splay_node_t *head; + splay_node_t *tail; + + /* Tree part */ + + splay_node_t *root; + + splay_compare_t compare; + splay_action_t delete; + + unsigned int count; + unsigned int generation; + +} splay_tree_t; + +/* (De)constructors */ + +extern splay_tree_t *splay_alloc_tree(splay_compare_t compare, splay_action_t delete) __attribute__((__malloc__)); +extern void splay_free_tree(splay_tree_t *tree); + +extern splay_node_t *splay_alloc_node(void) __attribute__((__malloc__)); +extern void splay_free_node(splay_tree_t *tree, splay_node_t *node); + +/* Insertion and deletion */ + +extern splay_node_t *splay_insert(splay_tree_t *tree, void *data); +extern splay_node_t *splay_insert_node(splay_tree_t *tree, splay_node_t *node); + +extern void splay_insert_top(splay_tree_t *tree, splay_node_t *node); +extern void splay_insert_before(splay_tree_t *tree, splay_node_t *before, splay_node_t *node); +extern void splay_insert_after(splay_tree_t *tree, splay_node_t *after, splay_node_t *node); + +extern splay_node_t *splay_unlink(splay_tree_t *tree, void *data); +extern void splay_unlink_node(splay_tree_t *tree, splay_node_t *node); +extern void splay_delete(splay_tree_t *tree, void *data); +extern void splay_delete_node(splay_tree_t *tree, splay_node_t *node); + +/* Fast tree cleanup */ + +extern void splay_delete_tree(splay_tree_t *tree); + +/* Searching */ + +extern void *splay_search(splay_tree_t *tree, const void *data); +extern void *splay_search_closest(splay_tree_t *tree, const void *data, int *result); +extern void *splay_search_closest_smaller(splay_tree_t *tree, const void *data); +extern void *splay_search_closest_greater(splay_tree_t *tree, const void *data); + +extern splay_node_t *splay_search_node(splay_tree_t *tree, const void *data); +extern splay_node_t *splay_search_closest_node(splay_tree_t *tree, const void *data, int *result); +extern splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *tree, const void *data, int *result); +extern splay_node_t *splay_search_closest_smaller_node(splay_tree_t *tree, const void *data); +extern splay_node_t *splay_search_closest_greater_node(splay_tree_t *tree, const void *data); + +/* Tree walking */ + +extern void splay_foreach(const splay_tree_t *tree, splay_action_t action); +extern void splay_foreach_node(const splay_tree_t *tree, splay_action_t action); + +/* + Iterates over a tree. + + CAUTION: while this construct supports deleting the current item, + it does *not* support deleting *other* nodes while iterating on the tree. + */ +#define splay_each(type, item, tree) (type *item = (type *)1; item; item = NULL) for(splay_node_t *node = (tree)->head, *next; item = node ? node->data : NULL, next = node ? node->next : NULL, node; node = next) + +#endif diff --git a/src/sptps.c b/src/sptps.c new file mode 100644 index 0000000..eeaf833 --- /dev/null +++ b/src/sptps.c @@ -0,0 +1,767 @@ +/* + sptps.c -- Simple Peer-to-Peer Security + Copyright (C) 2011-2015 Guus Sliepen , + 2010 Brandon L. Black + + 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 "chacha-poly1305/chacha-poly1305.h" +#include "crypto.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "logger.h" +#include "prf.h" +#include "sptps.h" + +unsigned int sptps_replaywin = 16; + +/* + Nonce MUST be exchanged first (done) + Signatures MUST be done over both nonces, to guarantee the signature is fresh + Otherwise: if ECDHE key of one side is compromised, it can be reused! + + Add explicit tag to beginning of structure to distinguish the client and server when signing. (done) + + Sign all handshake messages up to ECDHE kex with long-term public keys. (done) + + HMACed KEX finished message to prevent downgrade attacks and prove you have the right key material (done by virtue of Ed25519 over the whole ECDHE exchange?) + + Explicit close message needs to be added. + + Maybe do add some alert messages to give helpful error messages? Not more than TLS sends. + + Use counter mode instead of OFB. (done) + + Make sure ECC operations are fixed time (aka prevent side-channel attacks). +*/ + +void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap) { + (void)s; + (void)s_errno; + (void)format; + (void)ap; +} + +void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap) { + (void)s; + (void)s_errno; + + vfprintf(stderr, format, ap); + fputc('\n', stderr); +} + +void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap) = sptps_log_stderr; + +// Log an error message. +static bool error(sptps_t *s, int s_errno, const char *format, ...) { + (void)s; + (void)s_errno; + + if(format) { + va_list ap; + va_start(ap, format); + sptps_log(s, s_errno, format, ap); + va_end(ap); + } + + errno = s_errno; + return false; +} + +static void warning(sptps_t *s, const char *format, ...) { + va_list ap; + va_start(ap, format); + sptps_log(s, 0, format, ap); + va_end(ap); +} + +// Send a record (datagram version, accepts all record types, handles encryption and authentication). +static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + char buffer[len + 21UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = s->outseqno++; + uint32_t netseqno = ntohl(seqno); + + memcpy(buffer, &netseqno, 4); + buffer[4] = type; + memcpy(buffer + 5, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 4, len + 1, buffer + 4, NULL); + return s->send_data(s->handle, type, buffer, len + 21UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer, len + 5UL); + } +} +// Send a record (private version, accepts all record types, handles encryption and authentication). +static bool send_record_priv(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + if(s->datagram) { + return send_record_priv_datagram(s, type, data, len); + } + + char buffer[len + 19UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = s->outseqno++; + uint16_t netlen = htons(len); + + memcpy(buffer, &netlen, 2); + buffer[2] = type; + memcpy(buffer + 3, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + chacha_poly1305_encrypt(s->outcipher, seqno, buffer + 2, len + 1, buffer + 2, NULL); + return s->send_data(s->handle, type, buffer, len + 19UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer, len + 3UL); + } +} + +// Send an application record. +bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len) { + // Sanity checks: application cannot send data before handshake is finished, + // and only record types 0..127 are allowed. + if(!s->outstate) { + return error(s, EINVAL, "Handshake phase not finished yet"); + } + + if(type >= SPTPS_HANDSHAKE) { + return error(s, EINVAL, "Invalid application record type"); + } + + return send_record_priv(s, type, data, len); +} + +// Send a Key EXchange record, containing a random nonce and an ECDHE public key. +static bool send_kex(sptps_t *s) { + size_t keylen = ECDH_SIZE; + + // Make room for our KEX message, which we will keep around since send_sig() needs it. + if(s->mykex) { + return false; + } + + s->mykex = realloc(s->mykex, 1 + 32 + keylen); + + if(!s->mykex) { + return error(s, errno, strerror(errno)); + } + + // Set version byte to zero. + s->mykex[0] = SPTPS_VERSION; + + // Create a random nonce. + randomize(s->mykex + 1, 32); + + // Create a new ECDH public key. + if(!(s->ecdh = ecdh_generate_public(s->mykex + 1 + 32))) { + return error(s, EINVAL, "Failed to generate ECDH public key"); + } + + return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen); +} + +// Send a SIGnature record, containing an Ed25519 signature over both KEX records. +static bool send_sig(sptps_t *s) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(s->mykey); + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator, plus label + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + char sig[siglen]; + + msg[0] = s->initiator; + memcpy(msg + 1, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Sign the result. + if(!ecdsa_sign(s->mykey, msg, sizeof(msg), sig)) { + return error(s, EINVAL, "Failed to sign SIG record"); + } + + // Send the SIG exchange record. + return send_record_priv(s, SPTPS_HANDSHAKE, sig, sizeof(sig)); +} + +// Generate key material from the shared secret created from the ECDHE key exchange. +static bool generate_key_material(sptps_t *s, const char *shared, size_t len) { + // Initialise cipher and digest structures if necessary + if(!s->outstate) { + s->incipher = chacha_poly1305_init(); + s->outcipher = chacha_poly1305_init(); + + if(!s->incipher || !s->outcipher) { + return error(s, EINVAL, "Failed to open cipher"); + } + } + + // Allocate memory for key material + size_t keylen = 2 * CHACHA_POLY1305_KEYLEN; + + s->key = realloc(s->key, keylen); + + if(!s->key) { + return error(s, errno, strerror(errno)); + } + + // Create the HMAC seed, which is "key expansion" + session label + server nonce + client nonce + char seed[s->labellen + 64 + 13]; + memcpy(seed, "key expansion", 13); + + if(s->initiator) { + memcpy(seed + 13, s->mykex + 1, 32); + memcpy(seed + 45, s->hiskex + 1, 32); + } else { + memcpy(seed + 13, s->hiskex + 1, 32); + memcpy(seed + 45, s->mykex + 1, 32); + } + + memcpy(seed + 77, s->label, s->labellen); + + // Use PRF to generate the key material + if(!prf(shared, len, seed, s->labellen + 64 + 13, s->key, keylen)) { + return error(s, EINVAL, "Failed to generate key material"); + } + + return true; +} + +// Send an ACKnowledgement record. +static bool send_ack(sptps_t *s) { + return send_record_priv(s, SPTPS_HANDSHAKE, "", 0); +} + +// Receive an ACKnowledgement record. +static bool receive_ack(sptps_t *s, const char *data, uint16_t len) { + (void)data; + + if(len) { + return error(s, EIO, "Invalid ACK record length"); + } + + if(s->initiator) { + if(!chacha_poly1305_set_key(s->incipher, s->key)) { + return error(s, EINVAL, "Failed to set counter"); + } + } else { + if(!chacha_poly1305_set_key(s->incipher, s->key + CHACHA_POLY1305_KEYLEN)) { + return error(s, EINVAL, "Failed to set counter"); + } + } + + free(s->key); + s->key = NULL; + s->instate = true; + + return true; +} + +// Receive a Key EXchange record, respond by sending a SIG record. +static bool receive_kex(sptps_t *s, const char *data, uint16_t len) { + // Verify length of the HELLO record + if(len != 1 + 32 + ECDH_SIZE) { + return error(s, EIO, "Invalid KEX record length"); + } + + // Ignore version number for now. + + // Make a copy of the KEX message, send_sig() and receive_sig() need it + if(s->hiskex) { + return error(s, EINVAL, "Received a second KEX message before first has been processed"); + } + + s->hiskex = realloc(s->hiskex, len); + + if(!s->hiskex) { + return error(s, errno, strerror(errno)); + } + + memcpy(s->hiskex, data, len); + + if(s->initiator) { + return send_sig(s); + } else { + return true; + } +} + +// Receive a SIGnature record, verify it, if it passed, compute the shared secret and calculate the session keys. +static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(s->hiskey); + + // Verify length of KEX record. + if(len != siglen) { + return error(s, EIO, "Invalid KEX record length"); + } + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + + msg[0] = !s->initiator; + memcpy(msg + 1, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Verify signature. + if(!ecdsa_verify(s->hiskey, msg, sizeof(msg), data)) { + return error(s, EIO, "Failed to verify SIG record"); + } + + // Compute shared secret. + char shared[ECDH_SHARED_SIZE]; + + if(!ecdh_compute_shared(s->ecdh, s->hiskex + 1 + 32, shared)) { + return error(s, EINVAL, "Failed to compute ECDH shared secret"); + } + + s->ecdh = NULL; + + // Generate key material from shared secret. + if(!generate_key_material(s, shared, sizeof(shared))) { + return false; + } + + if(!s->initiator && !send_sig(s)) { + return false; + } + + free(s->mykex); + free(s->hiskex); + + s->mykex = NULL; + s->hiskex = NULL; + + // Send cipher change record + if(s->outstate && !send_ack(s)) { + return false; + } + + // TODO: only set new keys after ACK has been set/received + if(s->initiator) { + if(!chacha_poly1305_set_key(s->outcipher, s->key + CHACHA_POLY1305_KEYLEN)) { + return error(s, EINVAL, "Failed to set key"); + } + } else { + if(!chacha_poly1305_set_key(s->outcipher, s->key)) { + return error(s, EINVAL, "Failed to set key"); + } + } + + return true; +} + +// Force another Key EXchange (for testing purposes). +bool sptps_force_kex(sptps_t *s) { + if(!s->outstate || s->state != SPTPS_SECONDARY_KEX) { + return error(s, EINVAL, "Cannot force KEX in current state"); + } + + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Receive a handshake record. +static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) { + // Only a few states to deal with handshaking. + switch(s->state) { + case SPTPS_SECONDARY_KEX: + + // We receive a secondary KEX request, first respond by sending our own. + if(!send_kex(s)) { + return false; + } + + // Fall through + case SPTPS_KEX: + + // We have sent our KEX request, we expect our peer to sent one as well. + if(!receive_kex(s, data, len)) { + return false; + } + + s->state = SPTPS_SIG; + return true; + + case SPTPS_SIG: + + // If we already sent our secondary public ECDH key, we expect the peer to send his. + if(!receive_sig(s, data, len)) { + return false; + } + + if(s->outstate) { + s->state = SPTPS_ACK; + } else { + s->outstate = true; + + if(!receive_ack(s, NULL, 0)) { + return false; + } + + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + } + + return true; + + case SPTPS_ACK: + + // We expect a handshake message to indicate transition to the new keys. + if(!receive_ack(s, data, len)) { + return false; + } + + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + return true; + + // TODO: split ACK into a VERify and ACK? + default: + return error(s, EIO, "Invalid session state %d", s->state); + } +} + +static bool sptps_check_seqno(sptps_t *s, uint32_t seqno, bool update_state) { + // Replay protection using a sliding window of configurable size. + // s->inseqno is expected sequence number + // seqno is received sequence number + // s->late[] is a circular buffer, a 1 bit means a packet has not been received yet + // The circular buffer contains bits for sequence numbers from s->inseqno - s->replaywin * 8 to (but excluding) s->inseqno. + if(s->replaywin) { + if(seqno != s->inseqno) { + if(seqno >= s->inseqno + s->replaywin * 8) { + // Prevent packets that jump far ahead of the queue from causing many others to be dropped. + bool farfuture = s->farfuture < s->replaywin >> 2; + + if(update_state) { + s->farfuture++; + } + + if(farfuture) { + return update_state ? error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture) : false; + } + + // Unless we have seen lots of them, in which case we consider the others lost. + if(update_state) { + warning(s, "Lost %d packets\n", seqno - s->inseqno); + } + + if(update_state) { + // Mark all packets in the replay window as being late. + memset(s->late, 255, s->replaywin); + } + } else if(seqno < s->inseqno) { + // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it. + if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8))) { + return update_state ? error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno) : false; + } + } else if(update_state) { + // We missed some packets. Mark them in the bitmap as being late. + for(uint32_t i = s->inseqno; i < seqno; i++) { + s->late[(i / 8) % s->replaywin] |= 1 << i % 8; + } + } + } + + if(update_state) { + // Mark the current packet as not being late. + s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8); + s->farfuture = 0; + } + } + + if(update_state) { + if(seqno >= s->inseqno) { + s->inseqno = seqno + 1; + } + + if(!s->inseqno) { + s->received = 0; + } else { + s->received++; + } + } + + return true; +} + +// Check datagram for valid HMAC +bool sptps_verify_datagram(sptps_t *s, const void *vdata, size_t len) { + if(!s->instate || len < 21) { + return error(s, EIO, "Received short packet"); + } + + const char *data = vdata; + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + + if(!sptps_check_seqno(s, seqno, false)) { + return false; + } + + char buffer[len]; + size_t outlen; + return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen); +} + +// Receive incoming data, datagram version. +static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len) { + if(len < (s->instate ? 21 : 5)) { + return error(s, EIO, "Received short packet"); + } + + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + data += 4; + len -= 4; + + if(!s->instate) { + if(seqno != s->inseqno) { + return error(s, EIO, "Invalid packet seqno: %d != %d", seqno, s->inseqno); + } + + s->inseqno = seqno + 1; + + uint8_t type = *(data++); + len--; + + if(type != SPTPS_HANDSHAKE) { + return error(s, EIO, "Application record received before handshake finished"); + } + + return receive_handshake(s, data, len); + } + + // Decrypt + + char buffer[len]; + size_t outlen; + + if(!chacha_poly1305_decrypt(s->incipher, seqno, data, len, buffer, &outlen)) { + return error(s, EIO, "Failed to decrypt and verify packet"); + } + + if(!sptps_check_seqno(s, seqno, true)) { + return false; + } + + // Append a NULL byte for safety. + buffer[outlen] = 0; + + data = buffer; + len = outlen; + + uint8_t type = *(data++); + len--; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) { + return error(s, EIO, "Application record received before handshake finished"); + } + + if(!s->receive_record(s->handle, type, data, len)) { + return false; + } + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, data, len)) { + return false; + } + } else { + return error(s, EIO, "Invalid record type %d", type); + } + + return true; +} + +// Receive incoming data. Check if it contains a complete record, if so, handle it. +size_t sptps_receive_data(sptps_t *s, const void *vdata, size_t len) { + const char *data = vdata; + size_t total_read = 0; + + if(!s->state) { + return error(s, EIO, "Invalid session state zero"); + } + + if(s->datagram) { + return sptps_receive_data_datagram(s, data, len) ? len : false; + } + + // First read the 2 length bytes. + if(s->buflen < 2) { + size_t toread = 2 - s->buflen; + + if(toread > len) { + toread = len; + } + + memcpy(s->inbuf + s->buflen, data, toread); + + total_read += toread; + s->buflen += toread; + len -= toread; + data += toread; + + // Exit early if we don't have the full length. + if(s->buflen < 2) { + return total_read; + } + + // Get the length bytes + + memcpy(&s->reclen, s->inbuf, 2); + s->reclen = ntohs(s->reclen); + + // If we have the length bytes, ensure our buffer can hold the whole request. + s->inbuf = realloc(s->inbuf, s->reclen + 19UL); + + if(!s->inbuf) { + return error(s, errno, strerror(errno)); + } + + // Exit early if we have no more data to process. + if(!len) { + return total_read; + } + } + + // Read up to the end of the record. + size_t toread = s->reclen + (s->instate ? 19UL : 3UL) - s->buflen; + + if(toread > len) { + toread = len; + } + + memcpy(s->inbuf + s->buflen, data, toread); + total_read += toread; + s->buflen += toread; + + // If we don't have a whole record, exit. + if(s->buflen < s->reclen + (s->instate ? 19UL : 3UL)) { + return total_read; + } + + // Update sequence number. + + uint32_t seqno = s->inseqno++; + + // Check HMAC and decrypt. + if(s->instate) { + if(!chacha_poly1305_decrypt(s->incipher, seqno, s->inbuf + 2UL, s->reclen + 17UL, s->inbuf + 2UL, NULL)) { + return error(s, EINVAL, "Failed to decrypt and verify record"); + } + } + + // Append a NULL byte for safety. + s->inbuf[s->reclen + 3UL] = 0; + + uint8_t type = s->inbuf[2]; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) { + return error(s, EIO, "Application record received before handshake finished"); + } + + if(!s->receive_record(s->handle, type, s->inbuf + 3, s->reclen)) { + return false; + } + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, s->inbuf + 3, s->reclen)) { + return false; + } + } else { + return error(s, EIO, "Invalid record type %d", type); + } + + s->buflen = 0; + + return total_read; +} + +// Start a SPTPS session. +bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const void *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) { + // Initialise struct sptps + memset(s, 0, sizeof(*s)); + + s->handle = handle; + s->initiator = initiator; + s->datagram = datagram; + s->mykey = mykey; + s->hiskey = hiskey; + s->replaywin = sptps_replaywin; + + if(s->replaywin) { + s->late = malloc(s->replaywin); + + if(!s->late) { + return error(s, errno, strerror(errno)); + } + + memset(s->late, 0, s->replaywin); + } + + s->label = malloc(labellen); + + if(!s->label) { + return error(s, errno, strerror(errno)); + } + + if(!datagram) { + s->inbuf = malloc(7); + + if(!s->inbuf) { + return error(s, errno, strerror(errno)); + } + + s->buflen = 0; + } + + memcpy(s->label, label, labellen); + s->labellen = labellen; + + s->send_data = send_data; + s->receive_record = receive_record; + + // Do first KEX immediately + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Stop a SPTPS session. +bool sptps_stop(sptps_t *s) { + // Clean up any resources. + chacha_poly1305_exit(s->incipher); + chacha_poly1305_exit(s->outcipher); + ecdh_free(s->ecdh); + free(s->inbuf); + free(s->mykex); + free(s->hiskex); + free(s->key); + free(s->label); + free(s->late); + memset(s, 0, sizeof(*s)); + return true; +} diff --git a/src/sptps.h b/src/sptps.h new file mode 100644 index 0000000..f5686f4 --- /dev/null +++ b/src/sptps.h @@ -0,0 +1,95 @@ +#ifndef TINC_SPTPS_H +#define TINC_SPTPS_H + +/* + sptps.h -- Simple Peer-to-Peer Security + Copyright (C) 2011-2014 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 "chacha-poly1305/chacha-poly1305.h" +#include "ecdh.h" +#include "ecdsa.h" + +#define SPTPS_VERSION 0 + +// Record types +#define SPTPS_HANDSHAKE 128 // Key exchange and authentication +#define SPTPS_ALERT 129 // Warning or error messages +#define SPTPS_CLOSE 130 // Application closed the connection + +// Key exchange states +#define SPTPS_KEX 1 // Waiting for the first Key EXchange record +#define SPTPS_SECONDARY_KEX 2 // Ready to receive a secondary Key EXchange record +#define SPTPS_SIG 3 // Waiting for a SIGnature record +#define SPTPS_ACK 4 // Waiting for an ACKnowledgement record + +// Overhead for datagrams +#define SPTPS_DATAGRAM_OVERHEAD 21 + +typedef bool (*send_data_t)(void *handle, uint8_t type, const void *data, size_t len); +typedef bool (*receive_record_t)(void *handle, uint8_t type, const void *data, uint16_t len); + +typedef struct sptps { + bool initiator; + bool datagram; + int state; + + char *inbuf; + size_t buflen; + uint16_t reclen; + + bool instate; + chacha_poly1305_ctx_t *incipher; + uint32_t inseqno; + uint32_t received; + unsigned int replaywin; + unsigned int farfuture; + char *late; + + bool outstate; + chacha_poly1305_ctx_t *outcipher; + uint32_t outseqno; + + ecdsa_t *mykey; + ecdsa_t *hiskey; + ecdh_t *ecdh; + + char *mykex; + char *hiskex; + char *key; + char *label; + size_t labellen; + + void *handle; + send_data_t send_data; + receive_record_t receive_record; +} sptps_t; + +extern unsigned int sptps_replaywin; +extern void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap); +extern void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap); +extern void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap); +extern bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const void *label, size_t labellen, send_data_t send_data, receive_record_t receive_record); +extern bool sptps_stop(sptps_t *s); +extern bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len); +extern size_t sptps_receive_data(sptps_t *s, const void *data, size_t len); +extern bool sptps_force_kex(sptps_t *s); +extern bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len); + +#endif diff --git a/src/sptps_keypair.c b/src/sptps_keypair.c new file mode 100644 index 0000000..51a94ee --- /dev/null +++ b/src/sptps_keypair.c @@ -0,0 +1,123 @@ +/* + sptps_test.c -- Simple Peer-to-Peer Security test program + Copyright (C) 2011-2013 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 "crypto.h" +#include "ecdsagen.h" +#include "utils.h" + +static char *program_name; + +void logger(int level, int priority, const char *format, ...) { + (void)level; + (void)priority; + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fputc('\n', stderr); +} + +static void usage() { + fprintf(stderr, "Usage: %s [options] private_key_file public_key_file\n\n", program_name); + fprintf(stderr, "Valid options are:\n" + " --help Display this help and exit.\n" + "\n"); + fprintf(stderr, "Report bugs to tinc@tinc-vpn.org.\n"); +} + +static struct option const long_options[] = { + {"help", no_argument, NULL, 1}, + {NULL, 0, NULL, 0} +}; + +int main(int argc, char *argv[]) { + program_name = argv[0]; + int r; + int option_index = 0; + + while((r = getopt_long(argc, argv, "", long_options, &option_index)) != EOF) { + switch(r) { + case 0: /* long option */ + break; + + case '?': /* wrong options */ + usage(); + return 1; + + case 1: /* help */ + usage(); + return 0; + + default: + break; + } + } + + argc -= optind - 1; + argv += optind - 1; + + if(argc != 3) { + fprintf(stderr, "Wrong number of arguments.\n"); + usage(); + return 1; + } + + crypto_init(); + + ecdsa_t *key = ecdsa_generate(); + + if(!key) { + return 1; + } + + FILE *fp = fopen(argv[1], "w"); + + if(fp) { + if(!ecdsa_write_pem_private_key(key, fp)) { + fprintf(stderr, "Could not write ECDSA private key\n"); + return 1; + } + + fclose(fp); + } else { + fprintf(stderr, "Could not open '%s' for writing: %s\n", argv[1], strerror(errno)); + return 1; + } + + fp = fopen(argv[2], "w"); + + if(fp) { + if(!ecdsa_write_pem_public_key(key, fp)) { + fprintf(stderr, "Could not write ECDSA public key\n"); + } + + fclose(fp); + } else { + fprintf(stderr, "Could not open '%s' for writing: %s\n", argv[2], strerror(errno)); + return 1; + } + + return 0; +} diff --git a/src/sptps_speed.c b/src/sptps_speed.c new file mode 100644 index 0000000..db7314e --- /dev/null +++ b/src/sptps_speed.c @@ -0,0 +1,308 @@ +/* + sptps_speed.c -- SPTPS benchmark + Copyright (C) 2013-2014 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 "utils.h" + +#include + +#include "crypto.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "ecdsagen.h" +#include "sptps.h" + +// Symbols necessary to link with logger.o +bool send_request(void *c, const char *msg, ...) { + return false; +} +struct list_t *connection_list = NULL; +bool send_meta(void *c, const char *msg, int len) { + return false; +} +char *logfilename = NULL; +bool do_detach = false; +struct timeval now; + +static bool send_data(void *handle, uint8_t type, const void *data, size_t len) { + int fd = *(int *)handle; + send(fd, data, len, 0); + return true; +} + +static bool receive_record(void *handle, uint8_t type, const void *data, uint16_t len) { + return true; +} + +static void receive_data(sptps_t *sptps) { + char buf[4096], *bufp = buf; + int fd = *(int *)sptps->handle; + size_t len = recv(fd, buf, sizeof(buf), 0); + + while(len) { + size_t done = sptps_receive_data(sptps, bufp, len); + + if(!done) { + abort(); + } + + bufp += done; + len -= done; + } +} + +struct timespec start; +struct timespec end; +double elapsed; +double rate; +unsigned int count; + +static void clock_start() { + count = 0; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start); +} + +static bool clock_countto(double seconds) { + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end); + elapsed = end.tv_sec + end.tv_nsec * 1e-9 - start.tv_sec - start.tv_nsec * 1e-9; + + if(elapsed < seconds) { + return ++count; + } + + rate = count / elapsed; + return false; +} + +int main(int argc, char *argv[]) { + ecdsa_t *key1, *key2; + ecdh_t *ecdh1, *ecdh2; + sptps_t sptps1, sptps2; + char buf1[4096], buf2[4096], buf3[4096]; + double duration = argc > 1 ? atof(argv[1]) : 10; + + crypto_init(); + + randomize(buf1, sizeof(buf1)); + randomize(buf2, sizeof(buf2)); + randomize(buf3, sizeof(buf3)); + + // Key generation + + fprintf(stderr, "Generating keys for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + ecdsa_free(ecdsa_generate()); + } + + fprintf(stderr, "%17.2lf op/s\n", rate); + + key1 = ecdsa_generate(); + key2 = ecdsa_generate(); + + // Ed25519 signatures + + fprintf(stderr, "Ed25519 sign for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) + if(!ecdsa_sign(key1, buf1, 256, buf2)) { + return 1; + } + + fprintf(stderr, "%20.2lf op/s\n", rate); + + fprintf(stderr, "Ed25519 verify for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) + if(!ecdsa_verify(key1, buf1, 256, buf2)) { + fprintf(stderr, "Signature verification failed\n"); + return 1; + } + + fprintf(stderr, "%18.2lf op/s\n", rate); + + ecdh1 = ecdh_generate_public(buf1); + fprintf(stderr, "ECDH for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + ecdh2 = ecdh_generate_public(buf2); + + if(!ecdh2) { + return 1; + } + + if(!ecdh_compute_shared(ecdh2, buf1, buf3)) { + return 1; + } + } + + fprintf(stderr, "%28.2lf op/s\n", rate); + ecdh_free(ecdh1); + + // SPTPS authentication phase + + int fd[2]; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { + fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno)); + return 1; + } + + struct pollfd pfd[2] = {{fd[0], POLLIN}, {fd[1], POLLIN}}; + + fprintf(stderr, "SPTPS/TCP authenticate for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record); + sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record); + + while(poll(pfd, 2, 0)) { + if(pfd[0].revents) { + receive_data(&sptps1); + } + + if(pfd[1].revents) { + receive_data(&sptps2); + } + } + + sptps_stop(&sptps1); + sptps_stop(&sptps2); + } + + fprintf(stderr, "%10.2lf op/s\n", rate * 2); + + // SPTPS data + + sptps_start(&sptps1, fd + 0, true, false, key1, key2, "sptps_speed", 11, send_data, receive_record); + sptps_start(&sptps2, fd + 1, false, false, key2, key1, "sptps_speed", 11, send_data, receive_record); + + while(poll(pfd, 2, 0)) { + if(pfd[0].revents) { + receive_data(&sptps1); + } + + if(pfd[1].revents) { + receive_data(&sptps2); + } + } + + fprintf(stderr, "SPTPS/TCP transmit for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + if(!sptps_send_record(&sptps1, 0, buf1, 1451)) { + abort(); + } + + receive_data(&sptps2); + } + + rate *= 2 * 1451 * 8; + + if(rate > 1e9) { + fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9); + } else if(rate > 1e6) { + fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6); + } else if(rate > 1e3) { + fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3); + } + + sptps_stop(&sptps1); + sptps_stop(&sptps2); + + // SPTPS datagram authentication phase + + close(fd[0]); + close(fd[1]); + + if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)) { + fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno)); + return 1; + } + + fprintf(stderr, "SPTPS/UDP authenticate for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record); + sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record); + + while(poll(pfd, 2, 0)) { + if(pfd[0].revents) { + receive_data(&sptps1); + } + + if(pfd[1].revents) { + receive_data(&sptps2); + } + } + + sptps_stop(&sptps1); + sptps_stop(&sptps2); + } + + fprintf(stderr, "%10.2lf op/s\n", rate * 2); + + // SPTPS datagram data + + sptps_start(&sptps1, fd + 0, true, true, key1, key2, "sptps_speed", 11, send_data, receive_record); + sptps_start(&sptps2, fd + 1, false, true, key2, key1, "sptps_speed", 11, send_data, receive_record); + + while(poll(pfd, 2, 0)) { + if(pfd[0].revents) { + receive_data(&sptps1); + } + + if(pfd[1].revents) { + receive_data(&sptps2); + } + } + + fprintf(stderr, "SPTPS/UDP transmit for %lg seconds: ", duration); + + for(clock_start(); clock_countto(duration);) { + if(!sptps_send_record(&sptps1, 0, buf1, 1451)) { + abort(); + } + + receive_data(&sptps2); + } + + rate *= 2 * 1451 * 8; + + if(rate > 1e9) { + fprintf(stderr, "%14.2lf Gbit/s\n", rate / 1e9); + } else if(rate > 1e6) { + fprintf(stderr, "%14.2lf Mbit/s\n", rate / 1e6); + } else if(rate > 1e3) { + fprintf(stderr, "%14.2lf kbit/s\n", rate / 1e3); + } + + sptps_stop(&sptps1); + sptps_stop(&sptps2); + + // Clean up + + close(fd[0]); + close(fd[1]); + ecdsa_free(key1); + ecdsa_free(key2); + crypto_exit(); + + return 0; +} diff --git a/src/sptps_test.c b/src/sptps_test.c new file mode 100644 index 0000000..ea81a28 --- /dev/null +++ b/src/sptps_test.c @@ -0,0 +1,473 @@ +/* + sptps_test.c -- Simple Peer-to-Peer Security test program + Copyright (C) 2011-2014 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" + +#ifdef HAVE_LINUX +#include +#endif + +#include + +#include "crypto.h" +#include "ecdsa.h" +#include "sptps.h" +#include "utils.h" + +// Symbols necessary to link with logger.o +bool send_request(void *c, const char *msg, ...) { + (void)c; + (void)msg; + return false; +} + +struct list_t *connection_list = NULL; + +bool send_meta(void *c, const char *msg, int len) { + (void)c; + (void)msg; + (void)len; + return false; +} + +char *logfilename = NULL; +bool do_detach = false; +struct timeval now; + +static bool special; +static bool verbose; +static bool readonly; +static bool writeonly; +static int in = 0; +static int out = 1; +static int addressfamily = AF_UNSPEC; + +static bool send_data(void *handle, uint8_t type, const void *data, size_t len) { + (void)type; + char hex[len * 2 + 1]; + bin2hex(data, hex, len); + + if(verbose) { + fprintf(stderr, "Sending %d bytes of data:\n%s\n", (int)len, hex); + } + + const int *sock = handle; + + if((size_t)send(*sock, data, len, 0) != len) { + return false; + } + + return true; +} + +static bool receive_record(void *handle, uint8_t type, const void *data, uint16_t len) { + (void)handle; + + if(verbose) { + fprintf(stderr, "Received type %d record of %u bytes:\n", type, len); + } + + if(!writeonly) { + write(out, data, len); + } + + return true; +} + +static struct option const long_options[] = { + {"datagram", no_argument, NULL, 'd'}, + {"quit", no_argument, NULL, 'q'}, + {"readonly", no_argument, NULL, 'r'}, + {"writeonly", no_argument, NULL, 'w'}, + {"packet-loss", required_argument, NULL, 'L'}, + {"replay-window", required_argument, NULL, 'W'}, + {"special", no_argument, NULL, 's'}, + {"verbose", required_argument, NULL, 'v'}, + {"help", no_argument, NULL, 1}, + {NULL, 0, NULL, 0} +}; + +const char *program_name; + +static void usage() { + fprintf(stderr, "Usage: %s [options] my_ed25519_key_file his_ed25519_key_file [host] port\n\n", program_name); + fprintf(stderr, "Valid options are:\n" + " -d, --datagram Enable datagram mode.\n" + " -q, --quit Quit when EOF occurs on stdin.\n" + " -r, --readonly Only send data from the socket to stdout.\n" +#ifdef HAVE_LINUX + " -t, --tun Use a tun device instead of stdio.\n" +#endif + " -w, --writeonly Only send data from stdin to the socket.\n" + " -L, --packet-loss RATE Fake packet loss of RATE percent.\n" + " -R, --replay-window N Set replay window to N bytes.\n" + " -s, --special Enable special handling of lines starting with #, ^ and $.\n" + " -v, --verbose Display debug messages.\n" + " -4 Use IPv4.\n" + " -6 Use IPv6.\n" + "\n"); + fprintf(stderr, "Report bugs to tinc@tinc-vpn.org.\n"); +} + +int main(int argc, char *argv[]) { + program_name = argv[0]; + bool initiator = false; + bool datagram = false; +#ifdef HAVE_LINUX + bool tun = false; +#endif + int packetloss = 0; + int r; + int option_index = 0; + ecdsa_t *mykey = NULL, *hiskey = NULL; + bool quit = false; + + while((r = getopt_long(argc, argv, "dqrstwL:W:v46", long_options, &option_index)) != EOF) { + switch(r) { + case 0: /* long option */ + break; + + case 'd': /* datagram mode */ + datagram = true; + break; + + case 'q': /* close connection on EOF from stdin */ + quit = true; + break; + + case 'r': /* read only */ + readonly = true; + break; + + case 't': /* read only */ +#ifdef HAVE_LINUX + tun = true; +#else + fprintf(stderr, "--tun is only supported on Linux.\n"); + usage(); + return 1; +#endif + break; + + case 'w': /* write only */ + writeonly = true; + break; + + case 'L': /* packet loss rate */ + packetloss = atoi(optarg); + break; + + case 'W': /* replay window size */ + sptps_replaywin = atoi(optarg); + break; + + case 'v': /* be verbose */ + verbose = true; + break; + + case 's': /* special character handling */ + special = true; + break; + + case '?': /* wrong options */ + usage(); + return 1; + + case '4': /* IPv4 */ + addressfamily = AF_INET; + break; + + case '6': /* IPv6 */ + addressfamily = AF_INET6; + break; + + case 1: /* help */ + usage(); + return 0; + + default: + break; + } + } + + argc -= optind - 1; + argv += optind - 1; + + if(argc < 4 || argc > 5) { + fprintf(stderr, "Wrong number of arguments.\n"); + usage(); + return 1; + } + + if(argc > 4) { + initiator = true; + } + + srand(time(NULL)); + +#ifdef HAVE_LINUX + + if(tun) { + in = out = open("/dev/net/tun", O_RDWR | O_NONBLOCK); + + if(in < 0) { + fprintf(stderr, "Could not open tun device: %s\n", strerror(errno)); + return 1; + } + + struct ifreq ifr = { + .ifr_flags = IFF_TUN + }; + + if(ioctl(in, TUNSETIFF, &ifr)) { + fprintf(stderr, "Could not configure tun interface: %s\n", strerror(errno)); + return 1; + } + + ifr.ifr_name[IFNAMSIZ - 1] = 0; + fprintf(stderr, "Using tun interface %s\n", ifr.ifr_name); + } + +#endif + +#ifdef HAVE_MINGW + static struct WSAData wsa_state; + + if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { + return 1; + } + +#endif + + struct addrinfo *ai, hint; + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = addressfamily; + hint.ai_socktype = datagram ? SOCK_DGRAM : SOCK_STREAM; + hint.ai_protocol = datagram ? IPPROTO_UDP : IPPROTO_TCP; + hint.ai_flags = initiator ? 0 : AI_PASSIVE; + + if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) { + fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno)); + return 1; + } + + int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if(sock < 0) { + fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)); + + if(initiator) { + if(connect(sock, ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not connect to peer: %s\n", sockstrerror(sockerrno)); + return 1; + } + + fprintf(stderr, "Connected\n"); + } else { + if(bind(sock, ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not bind socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + if(!datagram) { + if(listen(sock, 1)) { + fprintf(stderr, "Could not listen on socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + fprintf(stderr, "Listening...\n"); + + sock = accept(sock, NULL, NULL); + + if(sock < 0) { + fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno)); + return 1; + } + } else { + fprintf(stderr, "Listening...\n"); + + char buf[65536]; + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + + if(recvfrom(sock, buf, sizeof(buf), MSG_PEEK, &addr, &addrlen) <= 0) { + fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + if(connect(sock, &addr, addrlen)) { + fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno)); + return 1; + } + } + + fprintf(stderr, "Connected\n"); + } + + crypto_init(); + + FILE *fp = fopen(argv[1], "r"); + + if(!fp) { + fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno)); + return 1; + } + + if(!(mykey = ecdsa_read_pem_private_key(fp))) { + return 1; + } + + fclose(fp); + + fp = fopen(argv[2], "r"); + + if(!fp) { + fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno)); + return 1; + } + + if(!(hiskey = ecdsa_read_pem_public_key(fp))) { + return 1; + } + + fclose(fp); + + if(verbose) { + fprintf(stderr, "Keys loaded\n"); + } + + sptps_t s; + + if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record)) { + return 1; + } + + while(true) { + if(writeonly && readonly) { + break; + } + + char buf[65535] = ""; + size_t readsize = datagram ? 1460u : sizeof(buf); + + fd_set fds; + FD_ZERO(&fds); +#ifndef HAVE_MINGW + + if(!readonly && s.instate) { + FD_SET(in, &fds); + } + +#endif + FD_SET(sock, &fds); + + if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0) { + return 1; + } + + if(FD_ISSET(in, &fds)) { + ssize_t len = read(in, buf, readsize); + + if(len < 0) { + fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno)); + return 1; + } + + if(len == 0) { + if(quit) { + break; + } + + readonly = true; + continue; + } + + if(special && buf[0] == '#') { + s.outseqno = atoi(buf + 1); + } + + if(special && buf[0] == '^') { + sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0); + } else if(special && buf[0] == '$') { + sptps_force_kex(&s); + + if(len > 1) { + sptps_send_record(&s, 0, buf, len); + } + } else if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, (len == 1 && buf[0] == '\n') ? 0 : buf[0] == '*' ? sizeof(buf) : (size_t)len)) { + return 1; + } + } + + if(FD_ISSET(sock, &fds)) { + ssize_t len = recv(sock, buf, sizeof(buf), 0); + + if(len < 0) { + fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + if(len == 0) { + fprintf(stderr, "Connection terminated by peer.\n"); + break; + } + + if(verbose) { + char hex[len * 2 + 1]; + bin2hex(buf, hex, len); + fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex); + } + + if(packetloss && (rand() % 100) < packetloss) { + if(verbose) { + fprintf(stderr, "Dropped.\n"); + } + + continue; + } + + char *bufp = buf; + + while(len) { + size_t done = sptps_receive_data(&s, bufp, len); + + if(!done) { + if(!datagram) { + return 1; + } + } + + bufp += done; + len -= done; + } + } + } + + if(!sptps_stop(&s)) { + return 1; + } + + return 0; +} diff --git a/src/subnet.c b/src/subnet.c index 154fd80..4fefda6 100644 --- a/src/subnet.c +++ b/src/subnet.c @@ -1,6 +1,6 @@ /* subnet.c -- handle subnet lookups and lists - Copyright (C) 2000-2019 Guus Sliepen , + Copyright (C) 2000-2017 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -20,164 +20,66 @@ #include "system.h" -#include "avl_tree.h" +#include "splay_tree.h" +#include "control_common.h" #include "device.h" +#include "hash.h" #include "logger.h" +#include "names.h" #include "net.h" #include "netutl.h" #include "node.h" -#include "process.h" +#include "script.h" #include "subnet.h" #include "utils.h" #include "xalloc.h" /* lists type of subnet */ -avl_tree_t *subnet_tree; +splay_tree_t *subnet_tree; /* Subnet lookup cache */ -static ipv4_t cache_ipv4_address[2]; -static subnet_t *cache_ipv4_subnet[2]; -static bool cache_ipv4_valid[2]; -static int cache_ipv4_slot; - -static ipv6_t cache_ipv6_address[2]; -static subnet_t *cache_ipv6_subnet[2]; -static bool cache_ipv6_valid[2]; -static int cache_ipv6_slot; - -static mac_t cache_mac_address[2]; -static subnet_t *cache_mac_subnet[2]; -static bool cache_mac_valid[2]; -static int cache_mac_slot; +static hash_t *ipv4_cache; +static hash_t *ipv6_cache; +static hash_t *mac_cache; void subnet_cache_flush(void) { - cache_ipv4_valid[0] = cache_ipv4_valid[1] = false; - cache_ipv6_valid[0] = cache_ipv6_valid[1] = false; - cache_mac_valid[0] = cache_mac_valid[1] = false; -} - -/* Subnet comparison */ - -static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) { - int result; - - result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(mac_t)); - - if(result) { - return result; - } - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) { - return result; - } - - return strcmp(a->owner->name, b->owner->name); -} - -static int subnet_compare_ipv4(const subnet_t *a, const subnet_t *b) { - int result; - - result = b->net.ipv4.prefixlength - a->net.ipv4.prefixlength; - - if(result) { - return result; - } - - result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t)); - - if(result) { - return result; - } - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) { - return result; - } - - return strcmp(a->owner->name, b->owner->name); -} - -static int subnet_compare_ipv6(const subnet_t *a, const subnet_t *b) { - int result; - - result = b->net.ipv6.prefixlength - a->net.ipv6.prefixlength; - - if(result) { - return result; - } - - result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t)); - - if(result) { - return result; - } - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) { - return result; - } - - return strcmp(a->owner->name, b->owner->name); -} - -int subnet_compare(const subnet_t *a, const subnet_t *b) { - int result; - - result = a->type - b->type; - - if(result) { - return result; - } - - switch(a->type) { - case SUBNET_MAC: - return subnet_compare_mac(a, b); - - case SUBNET_IPV4: - return subnet_compare_ipv4(a, b); - - case SUBNET_IPV6: - return subnet_compare_ipv6(a, b); - - default: - logger(LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!", - a->type); - exit(0); - } - - return 0; + hash_clear(ipv4_cache); + hash_clear(ipv6_cache); + hash_clear(mac_cache); } /* Initialising trees */ void init_subnets(void) { - subnet_tree = avl_alloc_tree((avl_compare_t) subnet_compare, (avl_action_t) free_subnet); + subnet_tree = splay_alloc_tree((splay_compare_t) subnet_compare, (splay_action_t) free_subnet); - subnet_cache_flush(); + ipv4_cache = hash_alloc(0x100, sizeof(ipv4_t)); + ipv6_cache = hash_alloc(0x100, sizeof(ipv6_t)); + mac_cache = hash_alloc(0x100, sizeof(mac_t)); } void exit_subnets(void) { - avl_delete_tree(subnet_tree); + splay_delete_tree(subnet_tree); + + hash_free(ipv4_cache); + hash_free(ipv6_cache); + hash_free(mac_cache); } -avl_tree_t *new_subnet_tree(void) { - return avl_alloc_tree((avl_compare_t) subnet_compare, NULL); +splay_tree_t *new_subnet_tree(void) { + return splay_alloc_tree((splay_compare_t) subnet_compare, NULL); } -void free_subnet_tree(avl_tree_t *subnet_tree) { - avl_delete_tree(subnet_tree); +void free_subnet_tree(splay_tree_t *subnet_tree) { + splay_delete_tree(subnet_tree); } /* Allocating and freeing space for subnets */ subnet_t *new_subnet(void) { - return xmalloc_and_zero(sizeof(subnet_t)); + return xzalloc(sizeof(subnet_t)); } void free_subnet(subnet_t *subnet) { @@ -189,271 +91,43 @@ void free_subnet(subnet_t *subnet) { void subnet_add(node_t *n, subnet_t *subnet) { subnet->owner = n; - avl_insert(subnet_tree, subnet); - avl_insert(n->subnet_tree, subnet); + splay_insert(subnet_tree, subnet); + + if(n) { + splay_insert(n->subnet_tree, subnet); + } subnet_cache_flush(); } void subnet_del(node_t *n, subnet_t *subnet) { - avl_delete(n->subnet_tree, subnet); - avl_delete(subnet_tree, subnet); + if(n) { + splay_delete(n->subnet_tree, subnet); + } + + splay_delete(subnet_tree, subnet); subnet_cache_flush(); } -/* Ascii representation of subnets */ - -bool str2net(subnet_t *subnet, const char *subnetstr) { - char str[1024]; - strncpy(str, subnetstr, sizeof(str)); - str[sizeof(str) - 1] = 0; - int consumed; - - int weight = 10; - char *weight_separator = strchr(str, '#'); - - if(weight_separator) { - char *weight_str = weight_separator + 1; - - if(sscanf(weight_str, "%d%n", &weight, &consumed) < 1) { - return false; - } - - if(weight_str[consumed]) { - return false; - } - - *weight_separator = 0; - } - - int prefixlength = -1; - char *prefixlength_separator = strchr(str, '/'); - - if(prefixlength_separator) { - char *prefixlength_str = prefixlength_separator + 1; - - if(sscanf(prefixlength_str, "%d%n", &prefixlength, &consumed) < 1) { - return false; - } - - if(prefixlength_str[consumed]) { - return false; - } - - *prefixlength_separator = 0; - - if(prefixlength < 0) { - return false; - } - } - - uint16_t x[8]; - - if(sscanf(str, "%hx:%hx:%hx:%hx:%hx:%hx%n", &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &consumed) >= 6 && !str[consumed]) { - /* - Normally we should check that each part has two digits to prevent ambiguities. - However, in old tinc versions net2str() will aggressively return MAC addresses with one-digit parts, - so we have to accept them otherwise we would be unable to parse ADD_SUBNET messages. - */ - if(prefixlength >= 0) { - return false; - } - - subnet->type = SUBNET_MAC; - subnet->weight = weight; - - for(int i = 0; i < 6; i++) { - subnet->net.mac.address.x[i] = x[i]; - } - - return true; - } - - if(sscanf(str, "%hu.%hu.%hu.%hu%n", &x[0], &x[1], &x[2], &x[3], &consumed) >= 4 && !str[consumed]) { - if(prefixlength == -1) { - prefixlength = 32; - } - - if(prefixlength > 32) { - return false; - } - - subnet->type = SUBNET_IPV4; - subnet->net.ipv4.prefixlength = prefixlength; - subnet->weight = weight; - - for(int i = 0; i < 4; i++) { - if(x[i] > 255) { - return false; - } - - subnet->net.ipv4.address.x[i] = x[i]; - } - - return true; - } - - /* IPv6 */ - - char *last_colon = strrchr(str, ':'); - - if(last_colon && sscanf(last_colon, ":%hu.%hu.%hu.%hu%n", &x[0], &x[1], &x[2], &x[3], &consumed) >= 4 && !last_colon[consumed]) { - /* Dotted quad suffix notation, convert to standard IPv6 notation */ - for(int i = 0; i < 4; i++) - if(x[i] > 255) { - return false; - } - - snprintf(last_colon, sizeof(str) - (last_colon - str), ":%02x%02x:%02x%02x", x[0], x[1], x[2], x[3]); - } - - char *double_colon = strstr(str, "::"); - - if(double_colon) { - /* Figure out how many zero groups we need to expand */ - int zero_group_count = 8; - - for(const char *cur = str; *cur; cur++) - if(*cur != ':') { - zero_group_count--; - - while(cur[1] && cur[1] != ':') { - cur++; - } - } - - if(zero_group_count < 1) { - return false; - } - - /* Split the double colon in the middle to make room for zero groups */ - double_colon++; - memmove(double_colon + (zero_group_count * 2 - 1), double_colon, strlen(double_colon) + 1); - - /* Write zero groups in the resulting gap, overwriting the second colon */ - for(int i = 0; i < zero_group_count; i++) { - memcpy(&double_colon[i * 2], "0:", 2); - } - - /* Remove any leading or trailing colons */ - if(str[0] == ':') { - memmove(&str[0], &str[1], strlen(&str[1]) + 1); - } - - if(str[strlen(str) - 1] == ':') { - str[strlen(str) - 1] = 0; - } - } - - if(sscanf(str, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx%n", - &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &consumed) >= 8 && !str[consumed]) { - if(prefixlength == -1) { - prefixlength = 128; - } - - if(prefixlength > 128) { - return false; - } - - subnet->type = SUBNET_IPV6; - subnet->net.ipv6.prefixlength = prefixlength; - subnet->weight = weight; - - for(int i = 0; i < 8; i++) { - subnet->net.ipv6.address.x[i] = htons(x[i]); - } - - return true; - } - - return false; -} - -bool net2str(char *netstr, int len, const subnet_t *subnet) { - if(!netstr || !subnet) { - logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", (void *)netstr, (void *)subnet); - return false; - } - - switch(subnet->type) { - case SUBNET_MAC: - snprintf(netstr, len, "%x:%x:%x:%x:%x:%x#%d", - subnet->net.mac.address.x[0], - subnet->net.mac.address.x[1], - subnet->net.mac.address.x[2], - subnet->net.mac.address.x[3], - subnet->net.mac.address.x[4], - subnet->net.mac.address.x[5], - subnet->weight); - break; - - case SUBNET_IPV4: - snprintf(netstr, len, "%u.%u.%u.%u/%d#%d", - subnet->net.ipv4.address.x[0], - subnet->net.ipv4.address.x[1], - subnet->net.ipv4.address.x[2], - subnet->net.ipv4.address.x[3], - subnet->net.ipv4.prefixlength, - subnet->weight); - break; - - case SUBNET_IPV6: - snprintf(netstr, len, "%x:%x:%x:%x:%x:%x:%x:%x/%d#%d", - ntohs(subnet->net.ipv6.address.x[0]), - ntohs(subnet->net.ipv6.address.x[1]), - ntohs(subnet->net.ipv6.address.x[2]), - ntohs(subnet->net.ipv6.address.x[3]), - ntohs(subnet->net.ipv6.address.x[4]), - ntohs(subnet->net.ipv6.address.x[5]), - ntohs(subnet->net.ipv6.address.x[6]), - ntohs(subnet->net.ipv6.address.x[7]), - subnet->net.ipv6.prefixlength, - subnet->weight); - break; - - default: - logger(LOG_ERR, - "net2str() was called with unknown subnet type %d, exiting!", - subnet->type); - exit(0); - } - - return true; -} - /* Subnet lookup routines */ subnet_t *lookup_subnet(const node_t *owner, const subnet_t *subnet) { - return avl_search(owner->subnet_tree, subnet); + return splay_search(owner->subnet_tree, subnet); } subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) { - subnet_t *p, *r = NULL; - avl_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_mac_valid[i]) { - continue; - } - - if(owner && cache_mac_subnet[i] && cache_mac_subnet[i]->owner != owner) { - continue; - } - - if(!memcmp(address, &cache_mac_address[i], sizeof(*address))) { - return cache_mac_subnet[i]; - } + if((r = hash_search(mac_cache, address))) { + return r; } // Search all subnets for a matching one - for(n = owner ? owner->subnet_tree->head : subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, owner ? owner->subnet_tree : subnet_tree) { if(!p || p->type != SUBNET_MAC) { continue; } @@ -461,7 +135,7 @@ subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) { if(!memcmp(address, &p->net.mac.address, sizeof(*address))) { r = p; - if(p->owner->status.reachable) { + if(!p->owner || p->owner->status.reachable) { break; } } @@ -469,36 +143,25 @@ subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) { // Cache the result - cache_mac_slot = !cache_mac_slot; - memcpy(&cache_mac_address[cache_mac_slot], address, sizeof(*address)); - cache_mac_subnet[cache_mac_slot] = r; - cache_mac_valid[cache_mac_slot] = true; + if(r) { + hash_insert(mac_cache, address, r); + } return r; } subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { - subnet_t *p, *r = NULL; - avl_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_ipv4_valid[i]) { - continue; - } - - if(!memcmp(address, &cache_ipv4_address[i], sizeof(*address))) { - return cache_ipv4_subnet[i]; - } + if((r = hash_search(ipv4_cache, address))) { + return r; } // Search all subnets for a matching one - for(n = subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, subnet_tree) { if(!p || p->type != SUBNET_IPV4) { continue; } @@ -506,7 +169,7 @@ subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { if(!maskcmp(address, &p->net.ipv4.address, p->net.ipv4.prefixlength)) { r = p; - if(p->owner->status.reachable) { + if(!p->owner || p->owner->status.reachable) { break; } } @@ -514,36 +177,25 @@ subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { // Cache the result - cache_ipv4_slot = !cache_ipv4_slot; - memcpy(&cache_ipv4_address[cache_ipv4_slot], address, sizeof(*address)); - cache_ipv4_subnet[cache_ipv4_slot] = r; - cache_ipv4_valid[cache_ipv4_slot] = true; + if(r) { + hash_insert(ipv4_cache, address, r); + } return r; } subnet_t *lookup_subnet_ipv6(const ipv6_t *address) { - subnet_t *p, *r = NULL; - avl_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_ipv6_valid[i]) { - continue; - } - - if(!memcmp(address, &cache_ipv6_address[i], sizeof(*address))) { - return cache_ipv6_subnet[i]; - } + if((r = hash_search(ipv6_cache, address))) { + return r; } // Search all subnets for a matching one - for(n = subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, subnet_tree) { if(!p || p->type != SUBNET_IPV6) { continue; } @@ -551,7 +203,7 @@ subnet_t *lookup_subnet_ipv6(const ipv6_t *address) { if(!maskcmp(address, &p->net.ipv6.address, p->net.ipv6.prefixlength)) { r = p; - if(p->owner->status.reachable) { + if(!p->owner || p->owner->status.reachable) { break; } } @@ -559,45 +211,39 @@ subnet_t *lookup_subnet_ipv6(const ipv6_t *address) { // Cache the result - cache_ipv6_slot = !cache_ipv6_slot; - memcpy(&cache_ipv6_address[cache_ipv6_slot], address, sizeof(*address)); - cache_ipv6_subnet[cache_ipv6_slot] = r; - cache_ipv6_valid[cache_ipv6_slot] = true; + if(r) { + hash_insert(ipv6_cache, address, r); + } return r; } void subnet_update(node_t *owner, subnet_t *subnet, bool up) { - avl_node_t *node; - int i; - char *envp[10] = {NULL}; char netstr[MAXNETSTR]; char *name, *address, *port; char empty[] = ""; // Prepare environment variables to be passed to the script - xasprintf(&envp[0], "NETNAME=%s", netname ? netname : ""); - xasprintf(&envp[1], "DEVICE=%s", device ? device : ""); - xasprintf(&envp[2], "INTERFACE=%s", iface ? iface : ""); - xasprintf(&envp[3], "NODE=%s", owner->name); - xasprintf(&envp[4], "NAME=%s", myself->name); + environment_t env; + environment_init(&env); + environment_add(&env, "NODE=%s", owner->name); if(owner != myself) { sockaddr2str(&owner->address, &address, &port); - // 5 and 6 are reserved for SUBNET and WEIGHT - xasprintf(&envp[7], "REMOTEADDRESS=%s", address); - xasprintf(&envp[8], "REMOTEPORT=%s", port); + environment_add(&env, "REMOTEADDRESS=%s", address); + environment_add(&env, "REMOTEPORT=%s", port); free(port); free(address); } + int env_subnet = environment_add(&env, NULL); + int env_weight = environment_add(&env, NULL); + name = up ? "subnet-up" : "subnet-down"; if(!subnet) { - for(node = owner->subnet_tree->head; node; node = node->next) { - subnet = node->data; - + for splay_each(subnet_t, subnet, owner->subnet_tree) { if(!net2str(netstr, sizeof(netstr), subnet)) { continue; } @@ -612,13 +258,10 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { } // Prepare the SUBNET and WEIGHT variables - free(envp[5]); - free(envp[6]); + environment_update(&env, env_subnet, "SUBNET=%s", netstr); + environment_update(&env, env_weight, "WEIGHT=%s", weight); - xasprintf(&envp[5], "SUBNET=%s", netstr); - xasprintf(&envp[6], "WEIGHT=%s", weight); - - execute_script(name, envp); + execute_script(name, &env); } } else { if(net2str(netstr, sizeof(netstr), subnet)) { @@ -632,34 +275,28 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { } // Prepare the SUBNET and WEIGHT variables - xasprintf(&envp[5], "SUBNET=%s", netstr); - xasprintf(&envp[6], "WEIGHT=%s", weight); + environment_update(&env, env_subnet, "SUBNET=%s", netstr); + environment_update(&env, env_weight, "WEIGHT=%s", weight); - execute_script(name, envp); + execute_script(name, &env); } } - for(i = 0; i < 9; i++) { - free(envp[i]); - } + environment_exit(&env); } -void dump_subnets(void) { - char netstr[MAXNETSTR]; - subnet_t *subnet; - avl_node_t *node; - - logger(LOG_DEBUG, "Subnet list:"); - - for(node = subnet_tree->head; node; node = node->next) { - subnet = node->data; +bool dump_subnets(connection_t *c) { + for splay_each(subnet_t, subnet, subnet_tree) { + char netstr[MAXNETSTR]; if(!net2str(netstr, sizeof(netstr), subnet)) { continue; } - logger(LOG_DEBUG, " %s owner %s", netstr, subnet->owner->name); + send_request(c, "%d %d %s %s", + CONTROL, REQ_DUMP_SUBNETS, + netstr, subnet->owner ? subnet->owner->name : "(broadcast)"); } - logger(LOG_DEBUG, "End of subnet list."); + return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS); } diff --git a/src/subnet.h b/src/subnet.h index 6fecca3..56d55d1 100644 --- a/src/subnet.h +++ b/src/subnet.h @@ -3,7 +3,7 @@ /* subnet.h -- header for subnet.c - Copyright (C) 2000-2009 Guus Sliepen , + Copyright (C) 2000-2021 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -22,12 +22,13 @@ */ #include "net.h" +#include "node.h" typedef enum subnet_type_t { SUBNET_MAC = 0, SUBNET_IPV4, SUBNET_IPV6, - SUBNET_TYPES, /* Guardian */ + SUBNET_TYPES /* Guardian */ } subnet_type_t; typedef struct subnet_mac_t { @@ -44,14 +45,12 @@ typedef struct subnet_ipv6_t { int prefixlength; } subnet_ipv6_t; -#include "node.h" - typedef struct subnet_t { - struct node_t *owner; /* the owner of this subnet */ + struct node_t *owner; /* the owner of this subnet */ - subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */ - time_t expires; /* expiry time */ - int weight; /* weight (higher value is higher priority) */ + subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */ + time_t expires; /* expiry time */ + int weight; /* weight (higher value is higher priority) */ /* And now for the actual subnet: */ @@ -64,24 +63,30 @@ typedef struct subnet_t { #define MAXNETSTR 64 -extern avl_tree_t *subnet_tree; +extern splay_tree_t *subnet_tree; +extern int subnet_compare(const struct subnet_t *a, const struct subnet_t *b); extern subnet_t *new_subnet(void) __attribute__((__malloc__)); extern void free_subnet(subnet_t *subnet); extern void init_subnets(void); extern void exit_subnets(void); -extern avl_tree_t *new_subnet_tree(void) __attribute__((__malloc__)); -extern void free_subnet_tree(avl_tree_t *subnet_tree); +extern splay_tree_t *new_subnet_tree(void) __attribute__((__malloc__)); +extern void free_subnet_tree(splay_tree_t *); extern void subnet_add(struct node_t *owner, subnet_t *subnet); extern void subnet_del(struct node_t *owner, subnet_t *subnet); extern void subnet_update(struct node_t *owner, subnet_t *subnet, bool up); +extern int maskcmp(const void *a, const void *b, int masklen); +extern void maskcpy(void *dest, const void *src, int masklen, int len); +extern void mask(void *mask, int masklen, int len); +extern bool subnetcheck(const subnet_t subnet); +extern bool maskcheck(const void *mask, int masklen, int len); extern bool net2str(char *netstr, int len, const subnet_t *subnet); extern bool str2net(subnet_t *subnet, const char *netstr); extern subnet_t *lookup_subnet(const struct node_t *owner, const subnet_t *subnet); extern subnet_t *lookup_subnet_mac(const struct node_t *owner, const mac_t *address); extern subnet_t *lookup_subnet_ipv4(const ipv4_t *address); extern subnet_t *lookup_subnet_ipv6(const ipv6_t *address); -extern void dump_subnets(void); +extern bool dump_subnets(struct connection_t *c); extern void subnet_cache_flush(void); #endif diff --git a/src/subnet_parse.c b/src/subnet_parse.c new file mode 100644 index 0000000..32b7e0a --- /dev/null +++ b/src/subnet_parse.c @@ -0,0 +1,501 @@ +/* + subnet_parse.c -- handle subnet parsing + Copyright (C) 2000-2021 Guus Sliepen , + 2000-2005 Ivo Timmermans + + 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 "logger.h" +#include "net.h" +#include "netutl.h" +#include "subnet.h" +#include "utils.h" +#include "xalloc.h" + +/* Changing this default will affect ADD_SUBNET messages - beware of inconsistencies between versions */ +static const int DEFAULT_WEIGHT = 10; + +/* Subnet mask handling */ + +int maskcmp(const void *va, const void *vb, int masklen) { + int i, m, result; + const char *a = va; + const char *b = vb; + + for(m = masklen, i = 0; m >= 8; m -= 8, i++) { + result = a[i] - b[i]; + + if(result) { + return result; + } + } + + if(m) + return (a[i] & (0x100 - (1 << (8 - m)))) - + (b[i] & (0x100 - (1 << (8 - m)))); + + return 0; +} + +void mask(void *va, int masklen, int len) { + int i; + char *a = va; + + i = masklen / 8; + masklen %= 8; + + if(masklen) { + a[i++] &= (0x100 - (1 << (8 - masklen))); + } + + for(; i < len; i++) { + a[i] = 0; + } +} + +void maskcpy(void *va, const void *vb, int masklen, int len) { + int i, m; + char *a = va; + const char *b = vb; + + for(m = masklen, i = 0; m >= 8; m -= 8, i++) { + a[i] = b[i]; + } + + if(m) { + a[i] = b[i] & (0x100 - (1 << (8 - m))); + i++; + } + + for(; i < len; i++) { + a[i] = 0; + } +} + +bool subnetcheck(const subnet_t subnet) { + if(((subnet.type == SUBNET_IPV4) + && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof(subnet.net.ipv4.address))) + || ((subnet.type == SUBNET_IPV6) + && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof(subnet.net.ipv6.address)))) { + return false; + } + + return true; +} + +bool maskcheck(const void *va, int masklen, int len) { + int i; + const char *a = va; + + i = masklen / 8; + masklen %= 8; + + if(masklen && a[i++] & (0xff >> masklen)) { + return false; + } + + for(; i < len; i++) + if(a[i] != 0) { + return false; + } + + return true; +} + +/* Subnet comparison */ + +static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) { + int result; + + result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof(a->net.mac.address)); + + if(result) { + return result; + } + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) { + return result; + } + + return strcmp(a->owner->name, b->owner->name); +} + +static int subnet_compare_ipv4(const subnet_t *a, const subnet_t *b) { + int result; + + result = b->net.ipv4.prefixlength - a->net.ipv4.prefixlength; + + if(result) { + return result; + } + + result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t)); + + if(result) { + return result; + } + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) { + return result; + } + + return strcmp(a->owner->name, b->owner->name); +} + +static int subnet_compare_ipv6(const subnet_t *a, const subnet_t *b) { + int result; + + result = b->net.ipv6.prefixlength - a->net.ipv6.prefixlength; + + if(result) { + return result; + } + + result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t)); + + if(result) { + return result; + } + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) { + return result; + } + + return strcmp(a->owner->name, b->owner->name); +} + +int subnet_compare(const subnet_t *a, const subnet_t *b) { + int result; + + result = a->type - b->type; + + if(result) { + return result; + } + + switch(a->type) { + case SUBNET_MAC: + return subnet_compare_mac(a, b); + + case SUBNET_IPV4: + return subnet_compare_ipv4(a, b); + + case SUBNET_IPV6: + return subnet_compare_ipv6(a, b); + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!", a->type); + exit(1); + } + + return 0; +} + +/* Ascii representation of subnets */ + +bool str2net(subnet_t *subnet, const char *subnetstr) { + char str[1024]; + strncpy(str, subnetstr, sizeof(str)); + str[sizeof(str) - 1] = 0; + int consumed; + + int weight = DEFAULT_WEIGHT; + char *weight_separator = strchr(str, '#'); + + if(weight_separator) { + char *weight_str = weight_separator + 1; + + if(sscanf(weight_str, "%d%n", &weight, &consumed) < 1) { + return false; + } + + if(weight_str[consumed]) { + return false; + } + + *weight_separator = 0; + } + + int prefixlength = -1; + char *prefixlength_separator = strchr(str, '/'); + + if(prefixlength_separator) { + char *prefixlength_str = prefixlength_separator + 1; + + if(sscanf(prefixlength_str, "%d%n", &prefixlength, &consumed) < 1) { + return false; + } + + if(prefixlength_str[consumed]) { + return false; + } + + *prefixlength_separator = 0; + + if(prefixlength < 0) { + return false; + } + } + + uint16_t x[8]; + + if(sscanf(str, "%hx:%hx:%hx:%hx:%hx:%hx%n", &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &consumed) >= 6 && !str[consumed]) { + /* + Normally we should check that each part has two digits to prevent ambiguities. + However, in old tinc versions net2str() will aggressively return MAC addresses with one-digit parts, + so we have to accept them otherwise we would be unable to parse ADD_SUBNET messages. + */ + if(prefixlength >= 0) { + return false; + } + + subnet->type = SUBNET_MAC; + subnet->weight = weight; + + for(int i = 0; i < 6; i++) { + subnet->net.mac.address.x[i] = x[i]; + } + + return true; + } + + if(sscanf(str, "%hu.%hu.%hu.%hu%n", &x[0], &x[1], &x[2], &x[3], &consumed) >= 4 && !str[consumed]) { + if(prefixlength == -1) { + prefixlength = 32; + } + + if(prefixlength > 32) { + return false; + } + + subnet->type = SUBNET_IPV4; + subnet->net.ipv4.prefixlength = prefixlength; + subnet->weight = weight; + + for(int i = 0; i < 4; i++) { + if(x[i] > 255) { + return false; + } + + subnet->net.ipv4.address.x[i] = x[i]; + } + + return true; + } + + /* IPv6 */ + + char *last_colon = strrchr(str, ':'); + + if(last_colon && sscanf(last_colon, ":%hu.%hu.%hu.%hu%n", &x[0], &x[1], &x[2], &x[3], &consumed) >= 4 && !last_colon[consumed]) { + /* Dotted quad suffix notation, convert to standard IPv6 notation */ + for(int i = 0; i < 4; i++) + if(x[i] > 255) { + return false; + } + + snprintf(last_colon, sizeof(str) - (last_colon - str), ":%02x%02x:%02x%02x", x[0], x[1], x[2], x[3]); + } + + char *double_colon = strstr(str, "::"); + + if(double_colon) { + /* Figure out how many zero groups we need to expand */ + int zero_group_count = 8; + + for(const char *cur = str; *cur; cur++) + if(*cur != ':') { + zero_group_count--; + + while(cur[1] && cur[1] != ':') { + cur++; + } + } + + if(zero_group_count < 1) { + return false; + } + + /* Split the double colon in the middle to make room for zero groups */ + double_colon++; + memmove(double_colon + (zero_group_count * 2 - 1), double_colon, strlen(double_colon) + 1); + + /* Write zero groups in the resulting gap, overwriting the second colon */ + for(int i = 0; i < zero_group_count; i++) { + memcpy(&double_colon[i * 2], "0:", 2); + } + + /* Remove any leading or trailing colons */ + if(str[0] == ':') { + memmove(&str[0], &str[1], strlen(&str[1]) + 1); + } + + if(str[strlen(str) - 1] == ':') { + str[strlen(str) - 1] = 0; + } + } + + if(sscanf(str, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx%n", + &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &consumed) >= 8 && !str[consumed]) { + if(prefixlength == -1) { + prefixlength = 128; + } + + if(prefixlength > 128) { + return false; + } + + subnet->type = SUBNET_IPV6; + subnet->net.ipv6.prefixlength = prefixlength; + subnet->weight = weight; + + for(int i = 0; i < 8; i++) { + subnet->net.ipv6.address.x[i] = htons(x[i]); + } + + return true; + } + + return false; +} + +bool net2str(char *netstr, int len, const subnet_t *subnet) { + if(!netstr || !subnet) { + logger(DEBUG_ALWAYS, LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", (void *)netstr, (void *)subnet); + return false; + } + + int result; + int prefixlength = -1; + + switch(subnet->type) { + case SUBNET_MAC: + result = snprintf(netstr, len, "%02x:%02x:%02x:%02x:%02x:%02x", + subnet->net.mac.address.x[0], + subnet->net.mac.address.x[1], + subnet->net.mac.address.x[2], + subnet->net.mac.address.x[3], + subnet->net.mac.address.x[4], + subnet->net.mac.address.x[5]); + netstr += result; + len -= result; + break; + + case SUBNET_IPV4: + result = snprintf(netstr, len, "%u.%u.%u.%u", + subnet->net.ipv4.address.x[0], + subnet->net.ipv4.address.x[1], + subnet->net.ipv4.address.x[2], + subnet->net.ipv4.address.x[3]); + netstr += result; + len -= result; + prefixlength = subnet->net.ipv4.prefixlength; + + if(prefixlength == 32) { + prefixlength = -1; + } + + break; + + case SUBNET_IPV6: { + /* Find the longest sequence of consecutive zeroes */ + int max_zero_length = 0; + int max_zero_length_index = 0; + int current_zero_length = 0; + int current_zero_length_index = 0; + + for(int i = 0; i < 8; i++) { + if(subnet->net.ipv6.address.x[i] != 0) { + current_zero_length = 0; + } else { + if(current_zero_length == 0) { + current_zero_length_index = i; + } + + current_zero_length++; + + if(current_zero_length > max_zero_length) { + max_zero_length = current_zero_length; + max_zero_length_index = current_zero_length_index; + } + } + } + + /* Print the address */ + for(int i = 0; i < 8;) { + if(max_zero_length > 1 && max_zero_length_index == i) { + /* Shorten the representation as per RFC 5952 */ + const char *const FORMATS[] = { "%.1s", "%.2s", "%.3s" }; + const char *const *format = &FORMATS[0]; + + if(i == 0) { + format++; + } + + if(i + max_zero_length == 8) { + format++; + } + + result = snprintf(netstr, len, *format, ":::"); + i += max_zero_length; + } else { + result = snprintf(netstr, len, "%x:", ntohs(subnet->net.ipv6.address.x[i])); + i++; + } + + netstr += result; + len -= result; + } + + /* Remove the trailing colon */ + netstr--; + len++; + *netstr = 0; + + prefixlength = subnet->net.ipv6.prefixlength; + + if(prefixlength == 128) { + prefixlength = -1; + } + + break; + } + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "net2str() was called with unknown subnet type %d, exiting!", subnet->type); + exit(1); + } + + if(prefixlength >= 0) { + result = snprintf(netstr, len, "/%d", prefixlength); + netstr += result; + len -= result; + } + + if(subnet->weight != DEFAULT_WEIGHT) { + snprintf(netstr, len, "#%d", subnet->weight); + } + + return true; +} diff --git a/src/system.h b/src/system.h index 14db7f5..f9675f9 100644 --- a/src/system.h +++ b/src/system.h @@ -4,7 +4,7 @@ /* system.h -- system headers Copyright (C) 1998-2005 Ivo Timmermans - 2003-2006 Guus Sliepen + 2003-2016 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 @@ -21,7 +21,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "config.h" +#include "../config.h" #include "have.h" @@ -33,8 +33,4 @@ #include "dropin.h" -#ifndef HAVE_SOCKLEN_T -typedef int socklen_t; -#endif - #endif diff --git a/src/tincctl.c b/src/tincctl.c new file mode 100644 index 0000000..97b08cb --- /dev/null +++ b/src/tincctl.c @@ -0,0 +1,3379 @@ +/* + tincctl.c -- Controlling a running tincd + Copyright (C) 2007-2021 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 + +#ifdef HAVE_READLINE +#include "readline/readline.h" +#include "readline/history.h" +#endif + +#include "xalloc.h" +#include "protocol.h" +#include "control_common.h" +#include "crypto.h" +#include "ecdsagen.h" +#include "fsck.h" +#include "info.h" +#include "invitation.h" +#include "names.h" +#include "rsagen.h" +#include "utils.h" +#include "tincctl.h" +#include "top.h" +#include "version.h" +#include "subnet.h" + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +static char **orig_argv; +static int orig_argc; + +/* If nonzero, display usage information and exit. */ +static bool show_help = false; + +/* If nonzero, print the version on standard output and exit. */ +static bool show_version = false; + +static char *name = NULL; +static char controlcookie[1025]; +char *tinc_conf = NULL; +char *hosts_dir = NULL; +struct timeval now; + +// Horrible global variables... +static int pid = 0; +int fd = -1; +char line[4096]; +static int code; +static int req; +static int result; +bool force = false; +bool tty = true; +bool confbasegiven = false; +bool netnamegiven = false; +char *scriptinterpreter = NULL; +char *scriptextension = ""; +static char *prompt; +char *device = NULL; +char *iface = NULL; +int debug_level = -1; + +static struct option const long_options[] = { + {"batch", no_argument, NULL, 'b'}, + {"config", required_argument, NULL, 'c'}, + {"net", required_argument, NULL, 'n'}, + {"help", no_argument, NULL, 1}, + {"version", no_argument, NULL, 2}, + {"pidfile", required_argument, NULL, 3}, + {"force", no_argument, NULL, 4}, + {NULL, 0, NULL, 0} +}; + +static void version(void) { + printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, + BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR); + printf("Copyright (C) 1998-2018 Ivo Timmermans, Guus Sliepen and others.\n" + "See the AUTHORS file for a complete list.\n\n" + "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" + "and you are welcome to redistribute it under certain conditions;\n" + "see the file COPYING for details.\n"); +} + +static void usage(bool status) { + if(status) { + fprintf(stderr, "Try `%s --help\' for more information.\n", program_name); + } else { + printf("Usage: %s [options] command\n\n", program_name); + printf("Valid options are:\n" + " -b, --batch Don't ask for anything (non-interactive mode).\n" + " -c, --config=DIR Read configuration options from DIR.\n" + " -n, --net=NETNAME Connect to net NETNAME.\n" + " --pidfile=FILENAME Read control cookie from FILENAME.\n" + " --force Force some commands to work despite warnings.\n" + " --help Display this help and exit.\n" + " --version Output version information and exit.\n" + "\n" + "Valid commands are:\n" + " init [name] Create initial configuration files.\n" + " get VARIABLE Print current value of VARIABLE\n" + " set VARIABLE VALUE Set VARIABLE to VALUE\n" + " add VARIABLE VALUE Add VARIABLE with the given VALUE\n" + " del VARIABLE [VALUE] Remove VARIABLE [only ones with watching VALUE]\n" + " start [tincd options] Start tincd.\n" + " stop Stop tincd.\n" + " restart [tincd options] Restart tincd.\n" + " reload Partially reload configuration of running tincd.\n" + " pid Show PID of currently running tincd.\n" +#ifdef DISABLE_LEGACY + " generate-keys Generate a new Ed25519 public/private key pair.\n" +#else + " generate-keys [bits] Generate new RSA and Ed25519 public/private key pairs.\n" + " generate-rsa-keys [bits] Generate a new RSA public/private key pair.\n" +#endif + " generate-ed25519-keys Generate a new Ed25519 public/private key pair.\n" + " dump Dump a list of one of the following things:\n" + " [reachable] nodes - all known nodes in the VPN\n" + " edges - all known connections in the VPN\n" + " subnets - all known subnets in the VPN\n" + " connections - all meta connections with ourself\n" + " [di]graph - graph of the VPN in dotty format\n" + " invitations - outstanding invitations\n" + " info NODE|SUBNET|ADDRESS Give information about a particular NODE, SUBNET or ADDRESS.\n" + " purge Purge unreachable nodes\n" + " debug N Set debug level\n" + " retry Retry all outgoing connections\n" + " disconnect NODE Close meta connection with NODE\n" +#ifdef HAVE_CURSES + " top Show real-time statistics\n" +#endif + " pcap [snaplen] Dump traffic in pcap format [up to snaplen bytes per packet]\n" + " log [level] Dump log output [up to the specified level]\n" + " export Export host configuration of local node to standard output\n" + " export-all Export all host configuration files to standard output\n" + " import Import host configuration file(s) from standard input\n" + " exchange Same as export followed by import\n" + " exchange-all Same as export-all followed by import\n" + " invite NODE [...] Generate an invitation for NODE\n" + " join INVITATION Join a VPN using an INVITATION\n" + " network [NETNAME] List all known networks, or switch to the one named NETNAME.\n" + " fsck Check the configuration files for problems.\n" + " sign [FILE] Generate a signed version of a file.\n" + " verify NODE [FILE] Verify that a file was signed by the given NODE.\n" + "\n"); + printf("Report bugs to tinc@tinc-vpn.org.\n"); + } +} + +static bool parse_options(int argc, char **argv) { + int r; + int option_index = 0; + + while((r = getopt_long(argc, argv, "+bc:n:", long_options, &option_index)) != EOF) { + switch(r) { + case 0: /* long option */ + break; + + case 'b': + tty = false; + break; + + case 'c': /* config file */ + confbase = xstrdup(optarg); + confbasegiven = true; + break; + + case 'n': /* net name given */ + netname = xstrdup(optarg); + break; + + case 1: /* show help */ + show_help = true; + break; + + case 2: /* show version */ + show_version = true; + break; + + case 3: /* open control socket here */ + pidfilename = xstrdup(optarg); + break; + + case 4: /* force */ + force = true; + break; + + case '?': /* wrong options */ + usage(true); + return false; + + default: + break; + } + } + + if(!netname && (netname = getenv("NETNAME"))) { + netname = xstrdup(netname); + } + + /* netname "." is special: a "top-level name" */ + + if(netname && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; + } + + if(netname && (strpbrk(netname, "\\/") || *netname == '.')) { + fprintf(stderr, "Invalid character in netname!\n"); + return false; + } + + return true; +} + +/* Open a file with the desired permissions, minus the umask. + Also, if we want to create an executable file, we call fchmod() + to set the executable bits. */ + +FILE *fopenmask(const char *filename, const char *mode, mode_t perms) { + mode_t mask = umask(0); + perms &= ~mask; + umask(~perms & 0777); + FILE *f = fopen(filename, mode); + + if(!f) { + fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + return NULL; + } + +#ifdef HAVE_FCHMOD + + if((perms & 0444) && f) { + fchmod(fileno(f), perms); + } + +#endif + umask(mask); + return f; +} + +static void disable_old_keys(const char *filename, const char *what) { + char tmpfile[PATH_MAX] = ""; + char buf[1024]; + bool disabled = false; + bool block = false; + bool error = false; + + FILE *r = fopen(filename, "r"); + FILE *w = NULL; + + if(!r) { + return; + } + + int result = snprintf(tmpfile, sizeof(tmpfile), "%s.tmp", filename); + + if(result < sizeof(tmpfile)) { + struct stat st = {.st_mode = 0600}; + fstat(fileno(r), &st); + w = fopenmask(tmpfile, "w", st.st_mode); + } + + while(fgets(buf, sizeof(buf), r)) { + if(!block && !strncmp(buf, "-----BEGIN ", 11)) { + if((strstr(buf, " ED25519 ") && strstr(what, "Ed25519")) || (strstr(buf, " RSA ") && strstr(what, "RSA"))) { + disabled = true; + block = true; + } + } + + bool ed25519pubkey = !strncasecmp(buf, "Ed25519PublicKey", 16) && strchr(" \t=", buf[16]) && strstr(what, "Ed25519"); + + if(ed25519pubkey) { + disabled = true; + } + + if(w) { + if(block || ed25519pubkey) { + fputc('#', w); + } + + if(fputs(buf, w) < 0) { + error = true; + break; + } + } + + if(block && !strncmp(buf, "-----END ", 9)) { + block = false; + } + } + + if(w) + if(fclose(w) < 0) { + error = true; + } + + if(ferror(r) || fclose(r) < 0) { + error = true; + } + + if(disabled) { + if(!w || error) { + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + + if(w) { + unlink(tmpfile); + } + + return; + } + +#ifdef HAVE_MINGW + // We cannot atomically replace files on Windows. + char bakfile[PATH_MAX] = ""; + snprintf(bakfile, sizeof(bakfile), "%s.bak", filename); + + if(rename(filename, bakfile) || rename(tmpfile, filename)) { + rename(bakfile, filename); +#else + + if(rename(tmpfile, filename)) { +#endif + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + } else { +#ifdef HAVE_MINGW + unlink(bakfile); +#endif + fprintf(stderr, "Warning: old key(s) found and disabled.\n"); + } + } + + unlink(tmpfile); +} + +static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask, mode_t perms) { + FILE *r; + char directory[PATH_MAX] = "."; + char buf[PATH_MAX]; + char buf2[PATH_MAX]; + +ask_filename: + + /* Check stdin and stdout */ + if(ask && tty) { + /* Ask for a file and/or directory name. */ + fprintf(stderr, "Please enter a file to save %s to [%s]: ", what, filename); + + if(fgets(buf, sizeof(buf), stdin) == NULL) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + return NULL; + } + + size_t len = strlen(buf); + + if(len) { + buf[--len] = 0; + } + + if(len) { + filename = buf; + } + } + +#ifdef HAVE_MINGW + + if(filename[0] != '\\' && filename[0] != '/' && !strchr(filename, ':')) { +#else + + if(filename[0] != '/') { +#endif + /* The directory is a relative path or a filename. */ + getcwd(directory, sizeof(directory)); + + if((size_t)snprintf(buf2, sizeof(buf2), "%s" SLASH "%s", directory, filename) >= sizeof(buf2)) { + fprintf(stderr, "Filename too long: %s" SLASH "%s\n", directory, filename); + + if(ask && tty) { + goto ask_filename; + } else { + return NULL; + } + } + + filename = buf2; + } + + disable_old_keys(filename, what); + + /* Open it first to keep the inode busy */ + + r = fopenmask(filename, mode, perms); + + if(!r) { + fprintf(stderr, "Error opening file `%s': %s\n", filename, strerror(errno)); + return NULL; + } + + return r; +} + +/* + Generate a public/private Ed25519 key pair, and ask for a file to store + them in. +*/ +static bool ed25519_keygen(bool ask) { + ecdsa_t *key; + FILE *f; + char fname[PATH_MAX]; + + fprintf(stderr, "Generating Ed25519 key pair:\n"); + + if(!(key = ecdsa_generate())) { + fprintf(stderr, "Error during key generation!\n"); + return false; + } else { + fprintf(stderr, "Done.\n"); + } + + snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.priv", confbase); + f = ask_and_open(fname, "private Ed25519 key", "a", ask, 0600); + + if(!f) { + goto error; + } + + if(!ecdsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Error writing private key!\n"); + goto error; + } + + fclose(f); + + if(name) { + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name); + } else { + snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.pub", confbase); + } + + f = ask_and_open(fname, "public Ed25519 key", "a", ask, 0666); + + if(!f) { + return false; + } + + char *pubkey = ecdsa_get_base64_public_key(key); + fprintf(f, "Ed25519PublicKey = %s\n", pubkey); + free(pubkey); + + fclose(f); + ecdsa_free(key); + + return true; + +error: + + if(f) { + fclose(f); + } + + ecdsa_free(key); + return false; +} + +#ifndef DISABLE_LEGACY +/* + Generate a public/private RSA key pair, and ask for a file to store + them in. +*/ +static bool rsa_keygen(int bits, bool ask) { + rsa_t *key; + FILE *f; + char fname[PATH_MAX]; + + // Make sure the key size is a multiple of 8 bits. + bits &= ~0x7; + + // Make sure that a valid key size is used. + if(bits < 1024 || bits > 8192) { + fprintf(stderr, "Invalid key size %d specified! It should be between 1024 and 8192 bits.\n", bits); + return false; + } else if(bits < 2048) { + fprintf(stderr, "WARNING: generating a weak %d bits RSA key! 2048 or more bits are recommended.\n", bits); + } + + fprintf(stderr, "Generating %d bits keys:\n", bits); + + if(!(key = rsa_generate(bits, 0x10001))) { + fprintf(stderr, "Error during key generation!\n"); + return false; + } else { + fprintf(stderr, "Done.\n"); + } + + snprintf(fname, sizeof(fname), "%s" SLASH "rsa_key.priv", confbase); + f = ask_and_open(fname, "private RSA key", "a", ask, 0600); + + if(!f) { + goto error; + } + + if(!rsa_write_pem_private_key(key, f)) { + fprintf(stderr, "Error writing private key!\n"); + goto error; + } + + fclose(f); + + if(name) { + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, name); + } else { + snprintf(fname, sizeof(fname), "%s" SLASH "rsa_key.pub", confbase); + } + + f = ask_and_open(fname, "public RSA key", "a", ask, 0666); + + if(!f) { + goto error; + } + + if(!rsa_write_pem_public_key(key, f)) { + fprintf(stderr, "Error writing public key!\n"); + goto error; + } + + fclose(f); + rsa_free(key); + + return true; + +error: + + if(f) { + fclose(f); + } + + rsa_free(key); + return false; +} +#endif + +char buffer[4096]; +size_t blen = 0; + +bool recvline(int fd, char *line, size_t len) { + char *newline = NULL; + + if(!fd) { + return false; + } + + while(!(newline = memchr(buffer, '\n', blen))) { + int result = recv(fd, buffer + blen, sizeof(buffer) - blen, 0); + + if(result == -1 && sockerrno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + blen += result; + } + + if((size_t)(newline - buffer) >= len) { + return false; + } + + len = newline - buffer; + + memcpy(line, buffer, len); + line[len] = 0; + memmove(buffer, newline + 1, blen - len - 1); + blen -= len + 1; + + return true; +} + +static bool recvdata(int fd, char *data, size_t len) { + while(blen < len) { + int result = recv(fd, buffer + blen, sizeof(buffer) - blen, 0); + + if(result == -1 && sockerrno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + blen += result; + } + + memcpy(data, buffer, len); + memmove(buffer, buffer + len, blen - len); + blen -= len; + + return true; +} + +bool sendline(int fd, char *format, ...) { + static char buffer[4096]; + char *p = buffer; + int blen; + va_list ap; + + va_start(ap, format); + blen = vsnprintf(buffer, sizeof(buffer), format, ap); + buffer[sizeof(buffer) - 1] = 0; + va_end(ap); + + if(blen < 1 || (size_t)blen >= sizeof(buffer)) { + return false; + } + + buffer[blen] = '\n'; + blen++; + + while(blen) { + int result = send(fd, p, blen, MSG_NOSIGNAL); + + if(result == -1 && sockerrno == EINTR) { + continue; + } else if(result <= 0) { + return false; + } + + p += result; + blen -= result; + } + + return true; +} + +static void pcap(int fd, FILE *out, uint32_t snaplen) { + sendline(fd, "%d %d %d", CONTROL, REQ_PCAP, snaplen); + char data[9018]; + + struct { + uint32_t magic; + uint16_t major; + uint16_t minor; + uint32_t tz_offset; + uint32_t tz_accuracy; + uint32_t snaplen; + uint32_t ll_type; + } header = { + 0xa1b2c3d4, + 2, 4, + 0, 0, + snaplen ? snaplen : sizeof(data), + 1, + }; + + struct { + uint32_t tv_sec; + uint32_t tv_usec; + uint32_t len; + uint32_t origlen; + } packet; + + struct timeval tv; + + fwrite(&header, sizeof(header), 1, out); + fflush(out); + + char line[32]; + + while(recvline(fd, line, sizeof(line))) { + int code, req, len; + int n = sscanf(line, "%d %d %d", &code, &req, &len); + gettimeofday(&tv, NULL); + + if(n != 3 || code != CONTROL || req != REQ_PCAP || len < 0 || (size_t)len > sizeof(data)) { + break; + } + + if(!recvdata(fd, data, len)) { + break; + } + + packet.tv_sec = tv.tv_sec; + packet.tv_usec = tv.tv_usec; + packet.len = len; + packet.origlen = len; + fwrite(&packet, sizeof(packet), 1, out); + fwrite(data, len, 1, out); + fflush(out); + } +} + +static void logcontrol(int fd, FILE *out, int level) { + sendline(fd, "%d %d %d", CONTROL, REQ_LOG, level); + char data[1024]; + char line[32]; + + while(recvline(fd, line, sizeof(line))) { + int code, req, len; + int n = sscanf(line, "%d %d %d", &code, &req, &len); + + if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || (size_t)len > sizeof(data)) { + break; + } + + if(!recvdata(fd, data, len)) { + break; + } + + fwrite(data, len, 1, out); + fputc('\n', out); + fflush(out); + } +} + +static bool stop_tincd(void) { + if(!connect_tincd(true)) { + return false; + } + + sendline(fd, "%d %d", CONTROL, REQ_STOP); + + while(recvline(fd, line, sizeof(line))) { + // wait for tincd to close the connection... + } + + close(fd); + pid = 0; + fd = -1; + + return true; +} + +#ifdef HAVE_MINGW +static bool remove_service(void) { + SC_HANDLE manager = NULL; + SC_HANDLE service = NULL; + SERVICE_STATUS status = {0}; + bool success = false; + + manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); + + if(!manager) { + fprintf(stderr, "Could not open service manager: %s\n", winerror(GetLastError())); + goto exit; + } + + service = OpenService(manager, identname, SERVICE_ALL_ACCESS); + + if(!service) { + if(GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) { + success = stop_tincd(); + } else { + fprintf(stderr, "Could not open %s service: %s\n", identname, winerror(GetLastError())); + } + + goto exit; + } + + if(!ControlService(service, SERVICE_CONTROL_STOP, &status)) { + fprintf(stderr, "Could not stop %s service: %s\n", identname, winerror(GetLastError())); + } else { + fprintf(stderr, "%s service stopped\n", identname); + } + + if(!DeleteService(service)) { + fprintf(stderr, "Could not remove %s service: %s\n", identname, winerror(GetLastError())); + goto exit; + } + + success = true; + +exit: + + if(service) { + CloseServiceHandle(service); + } + + if(manager) { + CloseServiceHandle(manager); + } + + if(success) { + fprintf(stderr, "%s service removed\n", identname); + } + + return success; +} +#endif + +bool connect_tincd(bool verbose) { + if(fd >= 0) { + fd_set r; + FD_ZERO(&r); + FD_SET(fd, &r); + struct timeval tv = {0, 0}; + + if(select(fd + 1, &r, NULL, NULL, &tv)) { + fprintf(stderr, "Previous connection to tincd lost, reconnecting.\n"); + close(fd); + fd = -1; + } else { + return true; + } + } + + FILE *f = fopen(pidfilename, "r"); + + if(!f) { + if(verbose) { + fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); + } + + return false; + } + + char host[129]; + char port[129]; + + if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) { + if(verbose) { + fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + } + + fclose(f); + return false; + } + + fclose(f); + +#ifndef HAVE_MINGW + + if((pid == 0) || (kill(pid, 0) && (errno == ESRCH))) { + fprintf(stderr, "Could not find tincd running at pid %d\n", pid); + /* clean up the stale socket and pid file */ + unlink(pidfilename); + unlink(unixsocketname); + return false; + } + + struct sockaddr_un sa; + + sa.sun_family = AF_UNIX; + + strncpy(sa.sun_path, unixsocketname, sizeof(sa.sun_path)); + + sa.sun_path[sizeof(sa.sun_path) - 1] = 0; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + + if(fd < 0) { + if(verbose) { + fprintf(stderr, "Cannot create UNIX socket: %s\n", sockstrerror(sockerrno)); + } + + return false; + } + + if(connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + if(verbose) { + fprintf(stderr, "Cannot connect to UNIX socket %s: %s\n", unixsocketname, sockstrerror(sockerrno)); + } + + close(fd); + fd = -1; + return false; + } + +#else + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + .ai_flags = 0, + }; + + struct addrinfo *res = NULL; + + if(getaddrinfo(host, port, &hints, &res) || !res) { + if(verbose) { + fprintf(stderr, "Cannot resolve %s port %s: %s\n", host, port, sockstrerror(sockerrno)); + } + + return false; + } + + fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); + + if(fd < 0) { + if(verbose) { + fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); + } + + return false; + } + + unsigned long arg = 0; + + if(ioctlsocket(fd, FIONBIO, &arg) != 0) { + if(verbose) { + fprintf(stderr, "System call `%s' failed: %s\n", "ioctlsocket", sockstrerror(sockerrno)); + } + } + + if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + if(verbose) { + fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); + } + + close(fd); + fd = -1; + return false; + } + + freeaddrinfo(res); +#endif + +#ifdef SO_NOSIGPIPE + static const int one = 1; + setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one)); +#endif + + sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT); + + char data[4096]; + int version; + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %4095s %d", &code, data, &version) != 3 || code != 0) { + if(verbose) { + fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); + } + + close(fd); + fd = -1; + return false; + } + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) { + if(verbose) { + fprintf(stderr, "Could not fully establish control socket connection\n"); + } + + close(fd); + fd = -1; + return false; + } + + return true; +} + + +static int cmd_start(int argc, char *argv[]) { + if(connect_tincd(false)) { + if(netname) { + fprintf(stderr, "A tincd is already running for net `%s' with pid %d.\n", netname, pid); + } else { + fprintf(stderr, "A tincd is already running with pid %d.\n", pid); + } + + return 0; + } + + char *c; + char *slash = strrchr(program_name, '/'); + +#ifdef HAVE_MINGW + + if((c = strrchr(program_name, '\\')) > slash) { + slash = c; + } + +#endif + + if(slash++) { + xasprintf(&c, "%.*stincd", (int)(slash - program_name), program_name); + } else { + c = "tincd"; + } + + int nargc = 0; + char **nargv = xzalloc((optind + argc) * sizeof(*nargv)); + + char *arg0 = c; +#ifdef HAVE_MINGW + /* + Windows has no real concept of an "argv array". A command line is just one string. + The CRT of the new process will decode the command line string to generate argv before calling main(), and (by convention) + it uses quotes to handle spaces in arguments. + Therefore we need to quote all arguments that might contain spaces. No, execvp() won't do that for us (see MSDN). + If we don't do that, then execvp() will run fine but any spaces in the filename contained in arg0 will bleed + into the next arguments when the spawned process' CRT parses its command line, resulting in chaos. + */ + xasprintf(&arg0, "\"%s\"", arg0); +#endif + nargv[nargc++] = arg0; + + for(int i = 1; i < optind; i++) { + nargv[nargc++] = orig_argv[i]; + } + + for(int i = 1; i < argc; i++) { + nargv[nargc++] = argv[i]; + } + +#ifdef HAVE_MINGW + int status = spawnvp(_P_WAIT, c, nargv); + + if(status == -1) { + fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno)); + return 1; + } + + return status; +#else + int pfd[2] = {-1, -1}; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, pfd)) { + fprintf(stderr, "Could not create umbilical socket: %s\n", strerror(errno)); + free(nargv); + return 1; + } + + pid_t pid = fork(); + + if(pid == -1) { + fprintf(stderr, "Could not fork: %s\n", strerror(errno)); + free(nargv); + return 1; + } + + if(!pid) { + close(pfd[0]); + char buf[100]; + snprintf(buf, sizeof(buf), "%d", pfd[1]); + setenv("TINC_UMBILICAL", buf, true); + exit(execvp(c, nargv)); + } else { + close(pfd[1]); + } + + free(nargv); + + int status = -1, result; +#ifdef SIGINT + signal(SIGINT, SIG_IGN); +#endif + + // Pass all log messages from the umbilical to stderr. + // A nul-byte right before closure means tincd started successfully. + bool failure = true; + char buf[1024]; + ssize_t len; + + while((len = read(pfd[0], buf, sizeof(buf))) > 0) { + failure = buf[len - 1]; + + if(!failure) { + len--; + } + + write(2, buf, len); + } + + if(len) { + failure = true; + } + + close(pfd[0]); + + // Make sure the child process is really gone. + result = waitpid(pid, &status, 0); + +#ifdef SIGINT + signal(SIGINT, SIG_DFL); +#endif + + if(failure || result != pid || !WIFEXITED(status) || WEXITSTATUS(status)) { + fprintf(stderr, "Error starting %s\n", c); + return 1; + } + + return 0; +#endif +} + +static int cmd_stop(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + +#ifdef HAVE_MINGW + return remove_service(); +#else + + if(!stop_tincd()) { + if(pid) { + if(kill(pid, SIGTERM)) { + fprintf(stderr, "Could not send TERM signal to process with PID %d: %s\n", pid, strerror(errno)); + return 1; + } + + fprintf(stderr, "Sent TERM signal to process with PID %d.\n", pid); + waitpid(pid, NULL, 0); + return 0; + } + + return 1; + } + + return 0; +#endif +} + +static int cmd_restart(int argc, char *argv[]) { + cmd_stop(1, argv); + return cmd_start(argc, argv); +} + +static int cmd_reload(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) { + fprintf(stderr, "Could not reload configuration.\n"); + return 1; + } + + return 0; + +} + +static int dump_invitations(void) { + char dname[PATH_MAX]; + snprintf(dname, sizeof(dname), "%s" SLASH "invitations", confbase); + DIR *dir = opendir(dname); + + if(!dir) { + if(errno == ENOENT) { + fprintf(stderr, "No outstanding invitations.\n"); + return 0; + } + + fprintf(stderr, "Cannot not read directory %s: %s\n", dname, strerror(errno)); + return 1; + } + + struct dirent *ent; + + bool found = false; + + while((ent = readdir(dir))) { + char buf[MAX_STRING_SIZE]; + + if(b64decode(ent->d_name, buf, 24) != 18) { + continue; + } + + char fname[PATH_MAX]; + + if((size_t)snprintf(fname, sizeof(fname), "%s" SLASH "%s", dname, ent->d_name) >= sizeof(fname)) { + fprintf(stderr, "Filename too long: %s" SLASH "%s\n", dname, ent->d_name); + continue; + } + + FILE *f = fopen(fname, "r"); + + if(!f) { + fprintf(stderr, "Cannot open %s: %s\n", fname, strerror(errno)); + continue; + } + + buf[0] = 0; + + if(!fgets(buf, sizeof(buf), f)) { + fprintf(stderr, "Invalid invitation file %s\n", fname); + fclose(f); + continue; + } + + fclose(f); + + char *eol = buf + strlen(buf); + + while(strchr("\t \r\n", *--eol)) { + *eol = 0; + } + + if(strncmp(buf, "Name = ", 7) || !check_id(buf + 7)) { + fprintf(stderr, "Invalid invitation file %s\n", fname); + continue; + } + + found = true; + printf("%s %s\n", ent->d_name, buf + 7); + } + + closedir(dir); + + if(!found) { + fprintf(stderr, "No outstanding invitations.\n"); + } + + return 0; +} + +static int cmd_dump(int argc, char *argv[]) { + bool only_reachable = false; + + if(argc > 2 && !strcasecmp(argv[1], "reachable")) { + if(strcasecmp(argv[2], "nodes")) { + fprintf(stderr, "`reachable' only supported for nodes.\n"); + usage(true); + return 1; + } + + only_reachable = true; + argv++; + argc--; + } + + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + usage(true); + return 1; + } + + if(!strcasecmp(argv[1], "invitations")) { + return dump_invitations(); + } + + if(!connect_tincd(true)) { + return 1; + } + + int do_graph = 0; + + if(!strcasecmp(argv[1], "nodes")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + } else if(!strcasecmp(argv[1], "edges")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + } else if(!strcasecmp(argv[1], "subnets")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + } else if(!strcasecmp(argv[1], "connections")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); + } else if(!strcasecmp(argv[1], "graph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = 1; + } else if(!strcasecmp(argv[1], "digraph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = 2; + } else { + fprintf(stderr, "Unknown dump type '%s'.\n", argv[1]); + usage(true); + return 1; + } + + if(do_graph == 1) { + printf("graph {\n"); + } else if(do_graph == 2) { + printf("digraph {\n"); + } + + while(recvline(fd, line, sizeof(line))) { + char node1[4096], node2[4096]; + int n = sscanf(line, "%d %d %4095s %4095s", &code, &req, node1, node2); + + if(n == 2) { + if(do_graph && req == REQ_DUMP_NODES) { + continue; + } else { + if(do_graph) { + printf("}\n"); + } + + return 0; + } + } + + if(n < 2) { + break; + } + + char node[4096]; + char id[4096]; + char from[4096]; + char to[4096]; + char subnet[4096]; + char host[4096]; + char port[4096]; + char local_host[4096]; + char local_port[4096]; + char via[4096]; + char nexthop[4096]; + int cipher, digest, maclength, compression, distance, socket, weight; + short int pmtu, minmtu, maxmtu; + unsigned int options, status_int; + node_status_t status; + long int last_state_change; + int udp_ping_rtt; + uint64_t in_packets, in_bytes, out_packets, out_bytes; + + switch(req) { + case REQ_DUMP_NODES: { + int n = sscanf(line, "%*d %*d %4095s %4095s %4095s port %4095s %d %d %d %d %x %x %4095s %4095s %d %hd %hd %hd %ld %d %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change, &udp_ping_rtt, &in_packets, &in_bytes, &out_packets, &out_bytes); + + if(n != 22) { + fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line); + return 1; + } + + memcpy(&status, &status_int, sizeof(status)); + + if(do_graph) { + const char *color = "black"; + + if(!strcmp(host, "MYSELF")) { + color = "green"; + } else if(!status.reachable) { + color = "red"; + } else if(strcmp(via, node)) { + color = "orange"; + } else if(!status.validkey) { + color = "black"; + } else if(minmtu > 0) { + color = "green"; + } + + printf(" \"%s\" [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\""); + } else { + if(only_reachable && !status.reachable) { + continue; + } + + printf("%s id %s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d) rx %"PRIu64" %"PRIu64" tx %"PRIu64" %"PRIu64, + node, id, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu, in_packets, in_bytes, out_packets, out_bytes); + + if(udp_ping_rtt != -1) { + printf(" rtt %d.%03d", udp_ping_rtt / 1000, udp_ping_rtt % 1000); + } + + printf("\n"); + } + } + break; + + case REQ_DUMP_EDGES: { + int n = sscanf(line, "%*d %*d %4095s %4095s %4095s port %4095s %4095s port %4095s %x %d", from, to, host, port, local_host, local_port, &options, &weight); + + if(n != 8) { + fprintf(stderr, "Unable to parse edge dump from tincd.\n"); + return 1; + } + + if(do_graph) { + float w = 1 + 65536.0 / weight; + + if(do_graph == 1 && strcmp(node1, node2) > 0) { + printf(" \"%s\" -- \"%s\" [w = %f, weight = %f];\n", node1, node2, w, w); + } else if(do_graph == 2) { + printf(" \"%s\" -> \"%s\" [w = %f, weight = %f];\n", node1, node2, w, w); + } + } else { + printf("%s to %s at %s port %s local %s port %s options %x weight %d\n", from, to, host, port, local_host, local_port, options, weight); + } + } + break; + + case REQ_DUMP_SUBNETS: { + int n = sscanf(line, "%*d %*d %4095s %4095s", subnet, node); + + if(n != 2) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + + printf("%s owner %s\n", strip_weight(subnet), node); + } + break; + + case REQ_DUMP_CONNECTIONS: { + int n = sscanf(line, "%*d %*d %4095s %4095s port %4095s %x %d %x", node, host, port, &options, &socket, &status_int); + + if(n != 6) { + fprintf(stderr, "Unable to parse connection dump from tincd.\n"); + return 1; + } + + printf("%s at %s port %s options %x socket %d status %x\n", node, host, port, options, socket, status_int); + } + break; + + default: + fprintf(stderr, "Unable to parse dump from tincd.\n"); + return 1; + } + } + + fprintf(stderr, "Error receiving dump.\n"); + return 1; +} + +static int cmd_purge(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + sendline(fd, "%d %d", CONTROL, REQ_PURGE); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) { + fprintf(stderr, "Could not purge old information.\n"); + return 1; + } + + return 0; +} + +static int cmd_debug(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + int debuglevel = atoi(argv[1]); + int origlevel; + + sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &origlevel) != 3 || code != CONTROL || req != REQ_SET_DEBUG) { + fprintf(stderr, "Could not set debug level.\n"); + return 1; + } + + fprintf(stderr, "Old level %d, new level %d.\n", origlevel, debuglevel); + return 0; +} + +static int cmd_retry(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + sendline(fd, "%d %d", CONTROL, REQ_RETRY); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) { + fprintf(stderr, "Could not retry outgoing connections.\n"); + return 1; + } + + return 0; +} + +static int cmd_connect(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!check_id(argv[1])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_CONNECT || result) { + fprintf(stderr, "Could not connect to %s.\n", argv[1]); + return 1; + } + + return 0; +} + +static int cmd_disconnect(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!check_id(argv[1])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]); + + if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_DISCONNECT || result) { + fprintf(stderr, "Could not disconnect %s.\n", argv[1]); + return 1; + } + + return 0; +} + +static int cmd_top(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + +#ifdef HAVE_CURSES + + if(!connect_tincd(true)) { + return 1; + } + + top(fd); + return 0; +#else + fprintf(stderr, "This version of tinc was compiled without support for the curses library.\n"); + return 1; +#endif +} + +static int cmd_pcap(int argc, char *argv[]) { + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0); + return 0; +} + +#ifdef SIGINT +static void sigint_handler(int sig) { + (void)sig; + + fprintf(stderr, "\n"); + shutdown(fd, SHUT_RDWR); +} +#endif + +static int cmd_log(int argc, char *argv[]) { + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + +#ifdef SIGINT + signal(SIGINT, sigint_handler); +#endif + + logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1); + +#ifdef SIGINT + signal(SIGINT, SIG_DFL); +#endif + + close(fd); + fd = -1; + return 0; +} + +static int cmd_pid(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!connect_tincd(true) || !pid) { + return 1; + } + + printf("%d\n", pid); + return 0; +} + +int rstrip(char *value) { + int len = strlen(value); + + while(len && strchr("\t\r\n ", value[len - 1])) { + value[--len] = 0; + } + + return len; +} + +char *get_my_name(bool verbose) { + FILE *f = fopen(tinc_conf, "r"); + + if(!f) { + if(verbose) { + fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno)); + } + + return NULL; + } + + char buf[4096]; + char *value; + + while(fgets(buf, sizeof(buf), f)) { + int len = strcspn(buf, "\t ="); + value = buf + len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + if(!rstrip(value)) { + continue; + } + + buf[len] = 0; + + if(strcasecmp(buf, "Name")) { + continue; + } + + if(*value) { + fclose(f); + return replace_name(value); + } + } + + fclose(f); + + if(verbose) { + fprintf(stderr, "Could not find Name in %s.\n", tinc_conf); + } + + return NULL; +} + +ecdsa_t *get_pubkey(FILE *f) { + char buf[4096]; + char *value; + + while(fgets(buf, sizeof(buf), f)) { + int len = strcspn(buf, "\t ="); + value = buf + len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + if(!rstrip(value)) { + continue; + } + + buf[len] = 0; + + if(strcasecmp(buf, "Ed25519PublicKey")) { + continue; + } + + if(*value) { + return ecdsa_set_base64_public_key(value); + } + } + + return NULL; +} + +const var_t variables[] = { + /* Server configuration */ + {"AddressFamily", VAR_SERVER | VAR_SAFE}, + {"AutoConnect", VAR_SERVER | VAR_SAFE}, + {"BindToAddress", VAR_SERVER | VAR_MULTIPLE}, + {"BindToInterface", VAR_SERVER}, + {"Broadcast", VAR_SERVER | VAR_SAFE}, + {"BroadcastSubnet", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE}, + {"ConnectTo", VAR_SERVER | VAR_MULTIPLE | VAR_SAFE}, + {"DecrementTTL", VAR_SERVER | VAR_SAFE}, + {"Device", VAR_SERVER}, + {"DeviceStandby", VAR_SERVER}, + {"DeviceType", VAR_SERVER}, + {"DirectOnly", VAR_SERVER | VAR_SAFE}, + {"Ed25519PrivateKeyFile", VAR_SERVER}, + {"ExperimentalProtocol", VAR_SERVER}, + {"Forwarding", VAR_SERVER}, + {"FWMark", VAR_SERVER}, + {"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE}, + {"Hostnames", VAR_SERVER}, + {"IffOneQueue", VAR_SERVER}, + {"Interface", VAR_SERVER}, + {"InvitationExpire", VAR_SERVER}, + {"KeyExpire", VAR_SERVER | VAR_SAFE}, + {"ListenAddress", VAR_SERVER | VAR_MULTIPLE}, + {"LocalDiscovery", VAR_SERVER | VAR_SAFE}, + {"LogLevel", VAR_SERVER}, + {"MACExpire", VAR_SERVER | VAR_SAFE}, + {"MaxConnectionBurst", VAR_SERVER | VAR_SAFE}, + {"MaxOutputBufferSize", VAR_SERVER | VAR_SAFE}, + {"MaxTimeout", VAR_SERVER | VAR_SAFE}, + {"Mode", VAR_SERVER | VAR_SAFE}, + {"Name", VAR_SERVER}, + {"PingInterval", VAR_SERVER | VAR_SAFE}, + {"PingTimeout", VAR_SERVER | VAR_SAFE}, + {"PriorityInheritance", VAR_SERVER}, + {"PrivateKey", VAR_SERVER | VAR_OBSOLETE}, + {"PrivateKeyFile", VAR_SERVER}, + {"ProcessPriority", VAR_SERVER}, + {"Proxy", VAR_SERVER}, + {"ReplayWindow", VAR_SERVER | VAR_SAFE}, + {"ScriptsExtension", VAR_SERVER}, + {"ScriptsInterpreter", VAR_SERVER}, + {"StrictSubnets", VAR_SERVER | VAR_SAFE}, + {"TunnelServer", VAR_SERVER | VAR_SAFE}, + {"UDPDiscovery", VAR_SERVER | VAR_SAFE}, + {"UDPDiscoveryKeepaliveInterval", VAR_SERVER | VAR_SAFE}, + {"UDPDiscoveryInterval", VAR_SERVER | VAR_SAFE}, + {"UDPDiscoveryTimeout", VAR_SERVER | VAR_SAFE}, + {"MTUInfoInterval", VAR_SERVER | VAR_SAFE}, + {"UDPInfoInterval", VAR_SERVER | VAR_SAFE}, + {"UDPRcvBuf", VAR_SERVER}, + {"UDPSndBuf", VAR_SERVER}, + {"UPnP", VAR_SERVER}, + {"UPnPDiscoverWait", VAR_SERVER}, + {"UPnPRefreshPeriod", VAR_SERVER}, + {"VDEGroup", VAR_SERVER}, + {"VDEPort", VAR_SERVER}, + /* Host configuration */ + {"Address", VAR_HOST | VAR_MULTIPLE}, + {"Cipher", VAR_SERVER | VAR_HOST}, + {"ClampMSS", VAR_SERVER | VAR_HOST | VAR_SAFE}, + {"Compression", VAR_SERVER | VAR_HOST | VAR_SAFE}, + {"Digest", VAR_SERVER | VAR_HOST}, + {"Ed25519PublicKey", VAR_HOST}, + {"Ed25519PublicKeyFile", VAR_SERVER | VAR_HOST}, + {"IndirectData", VAR_SERVER | VAR_HOST | VAR_SAFE}, + {"MACLength", VAR_SERVER | VAR_HOST}, + {"PMTU", VAR_SERVER | VAR_HOST}, + {"PMTUDiscovery", VAR_SERVER | VAR_HOST}, + {"Port", VAR_HOST}, + {"PublicKey", VAR_HOST | VAR_OBSOLETE}, + {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"Subnet", VAR_HOST | VAR_MULTIPLE | VAR_SAFE}, + {"TCPOnly", VAR_SERVER | VAR_HOST | VAR_SAFE}, + {"Weight", VAR_HOST | VAR_SAFE}, + {NULL, 0} +}; + +static int cmd_config(int argc, char *argv[]) { + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(strcasecmp(argv[0], "config")) { + argv--, argc++; + } + + int action = -2; + + if(!strcasecmp(argv[1], "get")) { + argv++, argc--; + } else if(!strcasecmp(argv[1], "add")) { + argv++, argc--, action = 1; + } else if(!strcasecmp(argv[1], "del")) { + argv++, argc--, action = -1; + } else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) { + argv++, argc--, action = 0; + } + + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + // Concatenate the rest of the command line + strncpy(line, argv[1], sizeof(line) - 1); + + for(int i = 2; i < argc; i++) { + strncat(line, " ", sizeof(line) - 1 - strlen(line)); + strncat(line, argv[i], sizeof(line) - 1 - strlen(line)); + } + + // Liberal parsing into node name, variable name and value. + char *node = NULL; + char *variable; + char *value; + int len; + + len = strcspn(line, "\t ="); + value = line + len; + value += strspn(value, "\t "); + + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + + line[len] = '\0'; + variable = strchr(line, '.'); + + if(variable) { + node = line; + *variable++ = 0; + } else { + variable = line; + } + + if(!*variable) { + fprintf(stderr, "No variable given.\n"); + return 1; + } + + if(action >= 0 && !*value) { + fprintf(stderr, "No value for variable given.\n"); + return 1; + } + + if(action < -1 && *value) { + action = 0; + } + + /* Some simple checks. */ + bool found = false; + bool warnonremove = false; + + for(int i = 0; variables[i].name; i++) { + if(strcasecmp(variables[i].name, variable)) { + continue; + } + + found = true; + variable = (char *)variables[i].name; + + if(!strcasecmp(variable, "Subnet")) { + subnet_t s = {0}; + + if(!str2net(&s, value)) { + fprintf(stderr, "Malformed subnet definition %s\n", value); + } + + if(!subnetcheck(s)) { + fprintf(stderr, "Network address and prefix length do not match: %s\n", value); + return 1; + } + } + + /* Discourage use of obsolete variables. */ + + if(variables[i].type & VAR_OBSOLETE && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is an obsolete variable!\n", variable); + } else { + fprintf(stderr, "%s is an obsolete variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Don't put server variables in host config files */ + + if(node && !(variables[i].type & VAR_HOST) && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is not a host configuration variable!\n", variable); + } else { + fprintf(stderr, "%s is not a host configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Should this go into our own host config file? */ + + if(!node && !(variables[i].type & VAR_SERVER)) { + node = get_my_name(true); + + if(!node) { + return 1; + } + } + + /* Change "add" into "set" for variables that do not allow multiple occurrences. + Turn on warnings when it seems variables might be removed unintentionally. */ + + if(action == 1 && !(variables[i].type & VAR_MULTIPLE)) { + warnonremove = true; + action = 0; + } else if(action == 0 && (variables[i].type & VAR_MULTIPLE)) { + warnonremove = true; + } + + break; + } + + if(node && !check_id(node)) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!found) { + if(force || action < 0) { + fprintf(stderr, "Warning: %s is not a known configuration variable!\n", variable); + } else { + fprintf(stderr, "%s: is not a known configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + // Open the right configuration file. + char filename[PATH_MAX]; + + if(node) { + snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, node); + } else { + snprintf(filename, sizeof(filename), "%s", tinc_conf); + } + + FILE *f = fopen(filename, "r"); + + if(!f) { + fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + char tmpfile[PATH_MAX]; + FILE *tf = NULL; + + if(action >= -1) { + if((size_t)snprintf(tmpfile, sizeof(tmpfile), "%s.config.tmp", filename) >= sizeof(tmpfile)) { + fprintf(stderr, "Filename too long: %s.config.tmp\n", filename); + return 1; + } + + tf = fopen(tmpfile, "w"); + + if(!tf) { + fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + fclose(f); + return 1; + } + } + + // Copy the file, making modifications on the fly, unless we are just getting a value. + char buf1[4096]; + char buf2[4096]; + bool set = false; + bool removed = false; + found = false; + + while(fgets(buf1, sizeof(buf1), f)) { + buf1[sizeof(buf1) - 1] = 0; + strncpy(buf2, buf1, sizeof(buf2)); + + // Parse line in a simple way + char *bvalue; + int len; + + len = strcspn(buf2, "\t ="); + bvalue = buf2 + len; + bvalue += strspn(bvalue, "\t "); + + if(*bvalue == '=') { + bvalue++; + bvalue += strspn(bvalue, "\t "); + } + + rstrip(bvalue); + buf2[len] = '\0'; + + // Did it match? + if(!strcasecmp(buf2, variable)) { + // Get + if(action < -1) { + found = true; + printf("%s\n", bvalue); + // Del + } else if(action == -1) { + if(!*value || !strcasecmp(bvalue, value)) { + removed = true; + continue; + } + + // Set + } else if(action == 0) { + // Warn if "set" was used for variables that can occur multiple times + if(warnonremove && strcasecmp(bvalue, value)) { + fprintf(stderr, "Warning: removing %s = %s\n", variable, bvalue); + } + + // Already set? Delete the rest... + if(set) { + continue; + } + + // Otherwise, replace. + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + set = true; + continue; + // Add + } else if(action > 0) { + // Check if we've already seen this variable with the same value + if(!strcasecmp(bvalue, value)) { + found = true; + } + } + } + + if(action >= -1) { + // Copy original line... + if(fputs(buf1, tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Add newline if it is missing... + if(*buf1 && buf1[strlen(buf1) - 1] != '\n') { + if(fputc('\n', tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + } + } + } + + // Make sure we read everything... + if(ferror(f) || !feof(f)) { + fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + if(fclose(f)) { + fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + // Add new variable if necessary. + if((action > 0 && !found) || (action == 0 && !set)) { + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + } + + if(action < -1) { + if(found) { + return 0; + } else { + fprintf(stderr, "No matching configuration variables found.\n"); + return 1; + } + } + + // Make sure we wrote everything... + if(fclose(tf)) { + fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Could we find what we had to remove? + if(action < 0 && !removed) { + remove(tmpfile); + fprintf(stderr, "No configuration variables deleted.\n"); + return 1; + } + + // Replace the configuration file with the new one +#ifdef HAVE_MINGW + + if(remove(filename)) { + fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno)); + return 1; + } + +#endif + + if(rename(tmpfile, filename)) { + fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno)); + return 1; + } + + // Silently try notifying a running tincd of changes. + if(connect_tincd(false)) { + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + } + + return 0; +} + +static bool try_bind(int port) { + struct addrinfo *ai = NULL, *aip; + struct addrinfo hint = { + .ai_flags = AI_PASSIVE, + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + + bool success = true; + char portstr[16]; + snprintf(portstr, sizeof(portstr), "%d", port); + + if(getaddrinfo(NULL, portstr, &hint, &ai) || !ai) { + return false; + } + + for(aip = ai; aip; aip = aip->ai_next) { + int fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP); + + if(!fd) { + success = false; + break; + } + + int result = bind(fd, ai->ai_addr, ai->ai_addrlen); + closesocket(fd); + + if(result) { + success = false; + break; + } + } + + freeaddrinfo(ai); + return success; +} + +int check_port(const char *name) { + if(try_bind(655)) { + return 655; + } + + fprintf(stderr, "Warning: could not bind to port 655. "); + + for(int i = 0; i < 100; i++) { + int port = 0x1000 + (rand() & 0x7fff); + + if(try_bind(port)) { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "hosts" SLASH "%s", confbase, name); + FILE *f = fopen(filename, "a"); + + if(!f) { + fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + fprintf(stderr, "Please change tinc's Port manually.\n"); + return 0; + } + + fprintf(f, "Port = %d\n", port); + fclose(f); + fprintf(stderr, "Tinc will instead listen on port %d.\n", port); + return port; + } + } + + fprintf(stderr, "Please change tinc's Port manually.\n"); + return 0; +} + +static int cmd_init(int argc, char *argv[]) { + if(!access(tinc_conf, F_OK)) { + fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); + return 1; + } + + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } else if(argc < 2) { + if(tty) { + char buf[1024]; + fprintf(stderr, "Enter the Name you want your tinc node to have: "); + + if(!fgets(buf, sizeof(buf), stdin)) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + return 1; + } + + int len = rstrip(buf); + + if(!len) { + fprintf(stderr, "No name given!\n"); + return 1; + } + + name = strdup(buf); + } else { + fprintf(stderr, "No Name given!\n"); + return 1; + } + } else { + name = strdup(argv[1]); + + if(!*name) { + fprintf(stderr, "No Name given!\n"); + return 1; + } + } + + if(!check_id(name)) { + fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n"); + return 1; + } + + if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno)); + return 1; + } + + if(mkdir(confbase, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno)); + return 1; + } + + if(mkdir(hosts_dir, 0777) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); + return 1; + } + + FILE *f = fopen(tinc_conf, "w"); + + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno)); + return 1; + } + + fprintf(f, "Name = %s\n", name); + fclose(f); + +#ifndef DISABLE_LEGACY + + if(!rsa_keygen(2048, false)) { + return 1; + } + +#endif + + if(!ed25519_keygen(false)) { + return 1; + } + + check_port(name); + +#ifndef HAVE_MINGW + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "tinc-up", confbase); + + if(access(filename, F_OK)) { + FILE *f = fopenmask(filename, "w", 0777); + + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return 1; + } + + fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE netmask \n"); + fclose(f); + } + +#endif + + return 0; + +} + +static int cmd_generate_keys(int argc, char *argv[]) { +#ifdef DISABLE_LEGACY + (void)argv; + + if(argc > 1) { +#else + + if(argc > 2) { +#endif + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!name) { + name = get_my_name(false); + } + +#ifndef DISABLE_LEGACY + + if(!rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true)) { + return 1; + } + +#endif + + if(!ed25519_keygen(true)) { + return 1; + } + + return 0; +} + +#ifndef DISABLE_LEGACY +static int cmd_generate_rsa_keys(int argc, char *argv[]) { + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!name) { + name = get_my_name(false); + } + + return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true); +} +#endif + +static int cmd_generate_ed25519_keys(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!name) { + name = get_my_name(false); + } + + return !ed25519_keygen(true); +} + +static int cmd_help(int argc, char *argv[]) { + (void)argc; + (void)argv; + + usage(false); + return 0; +} + +static int cmd_version(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + version(); + return 0; +} + +static int cmd_info(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!connect_tincd(true)) { + return 1; + } + + return info(fd, argv[1]); +} + +static const char *conffiles[] = { + "tinc.conf", + "tinc-up", + "tinc-down", + "subnet-up", + "subnet-down", + "host-up", + "host-down", + NULL, +}; + +static int cmd_edit(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + char filename[PATH_MAX] = ""; + + if(strncmp(argv[1], "hosts" SLASH, 6)) { + for(int i = 0; conffiles[i]; i++) { + if(!strcmp(argv[1], conffiles[i])) { + snprintf(filename, sizeof(filename), "%s" SLASH "%s", confbase, argv[1]); + break; + } + } + } else { + argv[1] += 6; + } + + if(!*filename) { + snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, argv[1]); + char *dash = strchr(argv[1], '-'); + + if(dash) { + *dash++ = 0; + + if((strcmp(dash, "up") && strcmp(dash, "down")) || !check_id(argv[1])) { + fprintf(stderr, "Invalid configuration filename.\n"); + return 1; + } + } + } + + char *command; +#ifndef HAVE_MINGW + const char *editor = getenv("VISUAL"); + + if(!editor) { + editor = getenv("EDITOR"); + } + + if(!editor) { + editor = "vi"; + } + + xasprintf(&command, "\"%s\" \"%s\"", editor, filename); +#else + xasprintf(&command, "edit \"%s\"", filename); +#endif + int result = system(command); + free(command); + + if(result) { + return result; + } + + // Silently try notifying a running tincd of changes. + if(connect_tincd(false)) { + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + } + + return 0; +} + +static int export(const char *name, FILE *out) { + char filename[PATH_MAX]; + snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name); + FILE *in = fopen(filename, "r"); + + if(!in) { + fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + fprintf(out, "Name = %s\n", name); + char buf[4096]; + + while(fgets(buf, sizeof(buf), in)) { + if(strcspn(buf, "\t =") != 4 || strncasecmp(buf, "Name", 4)) { + fputs(buf, out); + } + } + + if(ferror(in)) { + fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno)); + fclose(in); + return 1; + } + + fclose(in); + return 0; +} + +static int cmd_export(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + char *name = get_my_name(true); + + if(!name) { + return 1; + } + + int result = export(name, stdout); + + if(!tty) { + fclose(stdout); + } + + free(name); + return result; +} + +static int cmd_export_all(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + DIR *dir = opendir(hosts_dir); + + if(!dir) { + fprintf(stderr, "Could not open host configuration directory %s: %s\n", hosts_dir, strerror(errno)); + return 1; + } + + bool first = true; + int result = 0; + struct dirent *ent; + + while((ent = readdir(dir))) { + if(!check_id(ent->d_name)) { + continue; + } + + if(first) { + first = false; + } else { + printf("#---------------------------------------------------------------#\n"); + } + + result |= export(ent->d_name, stdout); + } + + closedir(dir); + + if(!tty) { + fclose(stdout); + } + + return result; +} + +static int cmd_import(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + FILE *in = stdin; + FILE *out = NULL; + + char buf[4096]; + char name[4096]; + char filename[PATH_MAX] = ""; + int count = 0; + bool firstline = true; + + while(fgets(buf, sizeof(buf), in)) { + if(sscanf(buf, "Name = %4095s", name) == 1) { + firstline = false; + + if(!check_id(name)) { + fprintf(stderr, "Invalid Name in input!\n"); + return 1; + } + + if(out) { + fclose(out); + } + + if((size_t)snprintf(filename, sizeof(filename), "%s" SLASH "%s", hosts_dir, name) >= sizeof(filename)) { + fprintf(stderr, "Filename too long: %s" SLASH "%s\n", hosts_dir, name); + return 1; + } + + if(!force && !access(filename, F_OK)) { + fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename); + out = NULL; + continue; + } + + out = fopen(filename, "w"); + + if(!out) { + fprintf(stderr, "Error creating configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + count++; + continue; + } else if(firstline) { + fprintf(stderr, "Junk at the beginning of the input, ignoring.\n"); + firstline = false; + } + + + if(!strcmp(buf, "#---------------------------------------------------------------#\n")) { + continue; + } + + if(out) { + if(fputs(buf, out) < 0) { + fprintf(stderr, "Error writing to host configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + } + } + + if(out) { + fclose(out); + } + + if(count) { + fprintf(stderr, "Imported %d host configuration files.\n", count); + return 0; + } else { + fprintf(stderr, "No host configuration files imported.\n"); + return 1; + } +} + +static int cmd_exchange(int argc, char *argv[]) { + return cmd_export(argc, argv) ? 1 : cmd_import(argc, argv); +} + +static int cmd_exchange_all(int argc, char *argv[]) { + return cmd_export_all(argc, argv) ? 1 : cmd_import(argc, argv); +} + +static int switch_network(char *name) { + if(strcmp(name, ".")) { + if(!check_netname(name, false)) { + fprintf(stderr, "Invalid character in netname!\n"); + return 1; + } + + if(!check_netname(name, true)) { + fprintf(stderr, "Warning: unsafe character in netname!\n"); + } + } + + if(fd >= 0) { + close(fd); + fd = -1; + } + + free_names(); + netname = strcmp(name, ".") ? xstrdup(name) : NULL; + make_names(false); + + free(tinc_conf); + free(hosts_dir); + free(prompt); + + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); + xasprintf(&prompt, "%s> ", identname); + + return 0; +} + +static int cmd_network(int argc, char *argv[]) { + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(argc == 2) { + return switch_network(argv[1]); + } + + DIR *dir = opendir(confdir); + + if(!dir) { + fprintf(stderr, "Could not read directory %s: %s\n", confdir, strerror(errno)); + return 1; + } + + struct dirent *ent; + + while((ent = readdir(dir))) { + if(*ent->d_name == '.') { + continue; + } + + if(!strcmp(ent->d_name, "tinc.conf")) { + printf(".\n"); + continue; + } + + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s/%s/tinc.conf", confdir, ent->d_name); + + if(!access(fname, R_OK)) { + printf("%s\n", ent->d_name); + } + } + + closedir(dir); + + return 0; +} + +static int cmd_fsck(int argc, char *argv[]) { + (void)argv; + + if(argc > 1) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + return fsck(orig_argv[0]); +} + +static void *readfile(FILE *in, size_t *len) { + size_t count = 0; + size_t bufsize = 4096; + char *buf = xmalloc(bufsize); + + while(!feof(in)) { + size_t read = fread(buf + count, 1, bufsize - count, in); + + if(!read) { + break; + } + + count += read; + + if(count >= bufsize) { + bufsize *= 2; + buf = xrealloc(buf, bufsize); + } + } + + if(len) { + *len = count; + } + + return buf; +} + +static int cmd_sign(int argc, char *argv[]) { + if(argc > 2) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + if(!name) { + name = get_my_name(true); + + if(!name) { + return 1; + } + } + + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "ed25519_key.priv", confbase); + FILE *fp = fopen(fname, "r"); + + if(!fp) { + fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno)); + return 1; + } + + ecdsa_t *key = ecdsa_read_pem_private_key(fp); + + if(!key) { + fprintf(stderr, "Could not read private key from %s\n", fname); + fclose(fp); + return 1; + } + + fclose(fp); + + FILE *in; + + if(argc == 2) { + in = fopen(argv[1], "rb"); + + if(!in) { + fprintf(stderr, "Could not open %s: %s\n", argv[1], strerror(errno)); + ecdsa_free(key); + return 1; + } + } else { + in = stdin; + } + + size_t len; + char *data = readfile(in, &len); + + if(in != stdin) { + fclose(in); + } + + if(!data) { + fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno)); + ecdsa_free(key); + return 1; + } + + // Ensure we sign our name and current time as well + long t = time(NULL); + char *trailer; + xasprintf(&trailer, " %s %ld", name, t); + int trailer_len = strlen(trailer); + + data = xrealloc(data, len + trailer_len); + memcpy(data + len, trailer, trailer_len); + free(trailer); + + char sig[87]; + + if(!ecdsa_sign(key, data, len + trailer_len, sig)) { + fprintf(stderr, "Error generating signature\n"); + free(data); + ecdsa_free(key); + return 1; + } + + b64encode(sig, sig, 64); + ecdsa_free(key); + + fprintf(stdout, "Signature = %s %ld %s\n", name, t, sig); + fwrite(data, len, 1, stdout); + + free(data); + return 0; +} + +static int cmd_verify(int argc, char *argv[]) { + if(argc < 2) { + fprintf(stderr, "Not enough arguments!\n"); + return 1; + } + + if(argc > 3) { + fprintf(stderr, "Too many arguments!\n"); + return 1; + } + + char *node = argv[1]; + + if(!strcmp(node, ".")) { + if(!name) { + name = get_my_name(true); + + if(!name) { + return 1; + } + } + + node = name; + } else if(!strcmp(node, "*")) { + node = NULL; + } else { + if(!check_id(node)) { + fprintf(stderr, "Invalid node name\n"); + return 1; + } + } + + FILE *in; + + if(argc == 3) { + in = fopen(argv[2], "rb"); + + if(!in) { + fprintf(stderr, "Could not open %s: %s\n", argv[2], strerror(errno)); + return 1; + } + } else { + in = stdin; + } + + size_t len; + char *data = readfile(in, &len); + + if(in != stdin) { + fclose(in); + } + + if(!data) { + fprintf(stderr, "Error reading %s: %s\n", argv[1], strerror(errno)); + return 1; + } + + char *newline = memchr(data, '\n', len); + + if(!newline || (newline - data > MAX_STRING_SIZE - 1)) { + fprintf(stderr, "Invalid input\n"); + free(data); + return 1; + } + + *newline++ = '\0'; + size_t skip = newline - data; + + char signer[MAX_STRING_SIZE] = ""; + char sig[MAX_STRING_SIZE] = ""; + long t = 0; + + if(sscanf(data, "Signature = %s %ld %s", signer, &t, sig) != 3 || strlen(sig) != 86 || !t || !check_id(signer)) { + fprintf(stderr, "Invalid input\n"); + free(data); + return 1; + } + + if(node && strcmp(node, signer)) { + fprintf(stderr, "Signature is not made by %s\n", node); + free(data); + return 1; + } + + if(!node) { + node = signer; + } + + char *trailer; + xasprintf(&trailer, " %s %ld", signer, t); + int trailer_len = strlen(trailer); + + data = xrealloc(data, len + trailer_len); + memcpy(data + len, trailer, trailer_len); + free(trailer); + + newline = data + skip; + + char fname[PATH_MAX]; + snprintf(fname, sizeof(fname), "%s" SLASH "hosts" SLASH "%s", confbase, node); + FILE *fp = fopen(fname, "r"); + + if(!fp) { + fprintf(stderr, "Could not open %s: %s\n", fname, strerror(errno)); + free(data); + return 1; + } + + ecdsa_t *key = get_pubkey(fp); + + if(!key) { + rewind(fp); + key = ecdsa_read_pem_public_key(fp); + } + + if(!key) { + fprintf(stderr, "Could not read public key from %s\n", fname); + fclose(fp); + free(data); + return 1; + } + + fclose(fp); + + if(b64decode(sig, sig, 86) != 64 || !ecdsa_verify(key, newline, len + trailer_len - (newline - data), sig)) { + fprintf(stderr, "Invalid signature\n"); + free(data); + ecdsa_free(key); + return 1; + } + + ecdsa_free(key); + + fwrite(newline, len - (newline - data), 1, stdout); + + free(data); + return 0; +} + +static const struct { + const char *command; + int (*function)(int argc, char *argv[]); + bool hidden; +} commands[] = { + {"start", cmd_start, false}, + {"stop", cmd_stop, false}, + {"restart", cmd_restart, false}, + {"reload", cmd_reload, false}, + {"dump", cmd_dump, false}, + {"list", cmd_dump, false}, + {"purge", cmd_purge, false}, + {"debug", cmd_debug, false}, + {"retry", cmd_retry, false}, + {"connect", cmd_connect, false}, + {"disconnect", cmd_disconnect, false}, + {"top", cmd_top, false}, + {"pcap", cmd_pcap, false}, + {"log", cmd_log, false}, + {"pid", cmd_pid, false}, + {"config", cmd_config, true}, + {"add", cmd_config, false}, + {"del", cmd_config, false}, + {"get", cmd_config, false}, + {"set", cmd_config, false}, + {"init", cmd_init, false}, + {"generate-keys", cmd_generate_keys, false}, +#ifndef DISABLE_LEGACY + {"generate-rsa-keys", cmd_generate_rsa_keys, false}, +#endif + {"generate-ed25519-keys", cmd_generate_ed25519_keys, false}, + {"help", cmd_help, false}, + {"version", cmd_version, false}, + {"info", cmd_info, false}, + {"edit", cmd_edit, false}, + {"export", cmd_export, false}, + {"export-all", cmd_export_all, false}, + {"import", cmd_import, false}, + {"exchange", cmd_exchange, false}, + {"exchange-all", cmd_exchange_all, false}, + {"invite", cmd_invite, false}, + {"join", cmd_join, false}, + {"network", cmd_network, false}, + {"fsck", cmd_fsck, false}, + {"sign", cmd_sign, false}, + {"verify", cmd_verify, false}, + {NULL, NULL, false}, +}; + +#ifdef HAVE_READLINE +static char *complete_command(const char *text, int state) { + static int i; + + if(!state) { + i = 0; + } else { + i++; + } + + while(commands[i].command) { + if(!commands[i].hidden && !strncasecmp(commands[i].command, text, strlen(text))) { + return xstrdup(commands[i].command); + } + + i++; + } + + return NULL; +} + +static char *complete_dump(const char *text, int state) { + const char *matches[] = {"reachable", "nodes", "edges", "subnets", "connections", "graph", NULL}; + static int i; + + if(!state) { + i = 0; + } else { + i++; + } + + while(matches[i]) { + if(!strncasecmp(matches[i], text, strlen(text))) { + return xstrdup(matches[i]); + } + + i++; + } + + return NULL; +} + +static char *complete_config(const char *text, int state) { + static int i; + + if(!state) { + i = 0; + } else { + i++; + } + + while(variables[i].name) { + char *dot = strchr(text, '.'); + + if(dot) { + if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) { + char *match; + xasprintf(&match, "%.*s.%s", (int)(dot - text), text, variables[i].name); + return match; + } + } else { + if(!strncasecmp(variables[i].name, text, strlen(text))) { + return xstrdup(variables[i].name); + } + } + + i++; + } + + return NULL; +} + +static char *complete_info(const char *text, int state) { + static int i; + + if(!state) { + i = 0; + + if(!connect_tincd(false)) { + return NULL; + } + + // Check the list of nodes + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + } + + while(recvline(fd, line, sizeof(line))) { + char item[4096]; + int n = sscanf(line, "%d %d %4095s", &code, &req, item); + + if(n == 2) { + i++; + + if(i >= 2) { + break; + } else { + continue; + } + } + + if(n != 3) { + fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i); + break; + } + + if(!strncmp(item, text, strlen(text))) { + return xstrdup(strip_weight(item)); + } + } + + return NULL; +} + +static char *complete_nothing(const char *text, int state) { + (void)text; + (void)state; + return NULL; +} + +static char **completion(const char *text, int start, int end) { + (void)end; + char **matches = NULL; + + if(!start) { + matches = rl_completion_matches(text, complete_command); + } else if(!strncasecmp(rl_line_buffer, "dump ", 5)) { + matches = rl_completion_matches(text, complete_dump); + } else if(!strncasecmp(rl_line_buffer, "add ", 4)) { + matches = rl_completion_matches(text, complete_config); + } else if(!strncasecmp(rl_line_buffer, "del ", 4)) { + matches = rl_completion_matches(text, complete_config); + } else if(!strncasecmp(rl_line_buffer, "get ", 4)) { + matches = rl_completion_matches(text, complete_config); + } else if(!strncasecmp(rl_line_buffer, "set ", 4)) { + matches = rl_completion_matches(text, complete_config); + } else if(!strncasecmp(rl_line_buffer, "info ", 5)) { + matches = rl_completion_matches(text, complete_info); + } + + return matches; +} +#endif + +static int cmd_shell(int argc, char *argv[]) { + xasprintf(&prompt, "%s> ", identname); + int result = 0; + char buf[4096]; + char *line = NULL; + int maxargs = argc + 16; + char **nargv = xmalloc(maxargs * sizeof(*nargv)); + + for(int i = 0; i < argc; i++) { + nargv[i] = argv[i]; + } + +#ifdef HAVE_READLINE + rl_readline_name = "tinc"; + rl_completion_entry_function = complete_nothing; + rl_attempted_completion_function = completion; + rl_filename_completion_desired = 0; + char *copy = NULL; +#endif + + while(true) { +#ifdef HAVE_READLINE + + if(tty) { + free(copy); + free(line); + rl_basic_word_break_characters = "\t\n "; + line = readline(prompt); + copy = line ? xstrdup(line) : NULL; + } else { + line = fgets(buf, sizeof(buf), stdin); + } + +#else + + if(tty) { + fputs(prompt, stdout); + } + + line = fgets(buf, sizeof(buf), stdin); +#endif + + if(!line) { + break; + } + + /* Ignore comments */ + + if(*line == '#') { + continue; + } + + /* Split */ + + int nargc = argc; + char *p = line + strspn(line, " \t\n"); + char *next = strtok(p, " \t\n"); + + while(p && *p) { + if(nargc >= maxargs) { + maxargs *= 2; + nargv = xrealloc(nargv, maxargs * sizeof(*nargv)); + } + + nargv[nargc++] = p; + p = next; + next = strtok(NULL, " \t\n"); + } + + if(nargc == argc) { + continue; + } + + if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit")) { +#ifdef HAVE_READLINE + free(copy); +#endif + free(nargv); + return result; + } + + bool found = false; + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(nargv[argc], commands[i].command)) { + result |= commands[i].function(nargc - argc - 1, nargv + argc + 1); + found = true; + break; + } + } + +#ifdef HAVE_READLINE + + if(tty && found) { + add_history(copy); + } + +#endif + + if(!found) { + fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]); + result |= 1; + } + } + +#ifdef HAVE_READLINE + free(copy); +#endif + free(nargv); + + if(tty) { + printf("\n"); + } + + return result; +} + + +int main(int argc, char *argv[]) { + program_name = argv[0]; + orig_argv = argv; + orig_argc = argc; + tty = isatty(0) && isatty(1); + + if(!parse_options(argc, argv)) { + return 1; + } + + make_names(false); + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); + + if(show_version) { + version(); + return 0; + } + + if(show_help) { + usage(false); + return 0; + } + +#ifdef HAVE_MINGW + static struct WSAData wsa_state; + + if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { + fprintf(stderr, "System call `%s' failed: %s\n", "WSAStartup", winerror(GetLastError())); + return false; + } + +#endif + + srand(time(NULL)); + crypto_init(); + + if(optind >= argc) { + return cmd_shell(argc, argv); + } + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(argv[optind], commands[i].command)) { + return commands[i].function(argc - optind, argv + optind); + } + } + + fprintf(stderr, "Unknown command `%s'.\n", argv[optind]); + usage(true); + return 1; +} diff --git a/src/tincctl.h b/src/tincctl.h new file mode 100644 index 0000000..3f7d003 --- /dev/null +++ b/src/tincctl.h @@ -0,0 +1,55 @@ +#ifndef TINC_TINCCTL_H +#define TINC_TINCCTL_H + +/* + tincctl.h -- header for tincctl.c. + Copyright (C) 2011-2016 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. +*/ + +extern bool tty; +extern bool force; +extern char line[4096]; +extern int fd; +extern char buffer[4096]; +extern size_t blen; +extern bool confbasegiven; +extern char *tinc_conf; +extern char *hosts_dir; + +#define VAR_SERVER 1 /* Should be in tinc.conf */ +#define VAR_HOST 2 /* Can be in host config file */ +#define VAR_MULTIPLE 4 /* Multiple statements allowed */ +#define VAR_OBSOLETE 8 /* Should not be used anymore */ +#define VAR_SAFE 16 /* Variable is safe when accepting invitations */ + +typedef struct { + const char *name; + int type; +} var_t; + +extern const var_t variables[]; + +extern int rstrip(char *value); +extern char *get_my_name(bool verbose); +extern bool connect_tincd(bool verbose); +extern bool sendline(int fd, char *format, ...); +extern bool recvline(int fd, char *line, size_t len); +extern int check_port(const char *name); +extern FILE *fopenmask(const char *filename, const char *mode, mode_t perms); +extern ecdsa_t *get_pubkey(FILE *f); + +#endif diff --git a/src/tincd.c b/src/tincd.c index 066ad9c..cf96b29 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -1,7 +1,7 @@ /* tincd.c -- the main file for tincd Copyright (C) 1998-2005 Ivo Timmermans - 2000-2019 Guus Sliepen + 2000-2021 Guus Sliepen 2008 Max Rijevski 2009 Michael Tokarev 2010 Julien Muchembled @@ -33,15 +33,6 @@ #include #endif -#include -#include -#include -#include -#ifndef OPENSSL_NO_ENGINE -#include -#endif -#include - #ifdef HAVE_LZO #include LZO1X_H #endif @@ -52,75 +43,66 @@ #include #endif -#ifdef HAVE_GETOPT_LONG -#include -#else -#include "getopt.h" -#endif - -#include "pidfile.h" - #include "conf.h" +#include "control.h" +#include "crypto.h" #include "device.h" +#include "event.h" #include "logger.h" +#include "names.h" #include "net.h" #include "netutl.h" #include "process.h" #include "protocol.h" #include "utils.h" #include "xalloc.h" - -/* The name this program was run with. */ -char *program_name = NULL; +#include "version.h" /* If nonzero, display usage information and exit. */ -bool show_help = false; +static bool show_help = false; /* If nonzero, print the version on standard output and exit. */ -bool show_version = false; - -/* If nonzero, it will attempt to kill a running tincd and exit. */ -int kill_tincd = 0; - -/* If nonzero, generate public/private keypair for this host/net. */ -int generate_keys = 0; +static bool show_version = false; /* If nonzero, use null ciphers and skip all key exchanges. */ bool bypass_security = false; +#ifdef HAVE_MLOCKALL /* If nonzero, disable swapping for this process. */ -bool do_mlock = false; +static bool do_mlock = false; +#endif +#ifndef HAVE_MINGW /* If nonzero, chroot to netdir after startup. */ static bool do_chroot = false; /* If !NULL, do setuid to given user after startup */ static const char *switchuser = NULL; +#endif /* If nonzero, write log entries to a separate file. */ bool use_logfile = false; -char *identname = NULL; /* program name for syslog */ -char *pidfilename = NULL; /* pid file location */ -char *logfilename = NULL; /* log file location */ -char **g_argv; /* a copy of the cmdline arguments */ +/* If nonzero, use syslog instead of stderr in no-detach mode. */ +bool use_syslog = false; + +char **g_argv; /* a copy of the cmdline arguments */ static int status = 1; static struct option const long_options[] = { {"config", required_argument, NULL, 'c'}, - {"kill", optional_argument, NULL, 'k'}, {"net", required_argument, NULL, 'n'}, {"help", no_argument, NULL, 1}, {"version", no_argument, NULL, 2}, {"no-detach", no_argument, NULL, 'D'}, - {"generate-keys", optional_argument, NULL, 'K'}, {"debug", optional_argument, NULL, 'd'}, {"bypass-security", no_argument, NULL, 3}, {"mlock", no_argument, NULL, 'L'}, {"chroot", no_argument, NULL, 'R'}, {"user", required_argument, NULL, 'U'}, {"logfile", optional_argument, NULL, 4}, + {"syslog", no_argument, NULL, 's'}, {"pidfile", required_argument, NULL, 5}, {"option", required_argument, NULL, 'o'}, {NULL, 0, NULL, 0} @@ -128,7 +110,6 @@ static struct option const long_options[] = { #ifdef HAVE_MINGW static struct WSAData wsa_state; -CRITICAL_SECTION mutex; int main2(int argc, char **argv); #endif @@ -138,20 +119,24 @@ static void usage(bool status) { program_name); else { printf("Usage: %s [option]...\n\n", program_name); - printf(" -c, --config=DIR Read configuration options from DIR.\n" - " -D, --no-detach Don't fork and detach.\n" - " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n" - " -k, --kill[=SIGNAL] Attempt to kill a running tincd and exit.\n" - " -n, --net=NETNAME Connect to net NETNAME.\n" - " -K, --generate-keys[=BITS] Generate public/private RSA keypair.\n" - " -L, --mlock Lock tinc into main memory.\n" - " --logfile[=FILENAME] Write log entries to a logfile.\n" - " --pidfile=FILENAME Write PID to FILENAME.\n" - " -o, --option=[HOST.]KEY=VALUE Set global/host configuration value.\n" - " -R, --chroot chroot to NET dir at startup.\n" - " -U, --user=USER setuid to given USER at startup.\n" - " --help Display this help and exit.\n" - " --version Output version information and exit.\n\n"); + printf(" -c, --config=DIR Read configuration options from DIR.\n" + " -D, --no-detach Don't fork and detach.\n" + " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n" + " -n, --net=NETNAME Connect to net NETNAME.\n" +#ifdef HAVE_MLOCKALL + " -L, --mlock Lock tinc into main memory.\n" +#endif + " --logfile[=FILENAME] Write log entries to a logfile.\n" + " -s --syslog Use syslog instead of stderr with --no-detach.\n" + " --pidfile=FILENAME Write PID and control socket cookie to FILENAME.\n" + " --bypass-security Disables meta protocol security, for debugging.\n" + " -o, --option[HOST.]KEY=VALUE Set global/host configuration value.\n" +#ifndef HAVE_MINGW + " -R, --chroot chroot to NET dir at startup.\n" + " -U, --user=USER setuid to given USER at startup.\n" +#endif + " --help Display this help and exit.\n" + " --version Output version information and exit.\n\n"); printf("Report bugs to tinc@tinc-vpn.org.\n"); } } @@ -164,35 +149,29 @@ static bool parse_options(int argc, char **argv) { cmdline_conf = list_alloc((list_action_t)free_config); - while((r = getopt_long(argc, argv, "c:DLd::k::n:o:K::RU:", long_options, &option_index)) != EOF) { + while((r = getopt_long(argc, argv, "c:DLd::n:so:RU:", long_options, &option_index)) != EOF) { switch(r) { - case 0: /* long option */ + case 0: /* long option */ break; - case 'c': /* config file */ - if(confbase) { - fprintf(stderr, "Only one configuration directory can be given.\n"); - usage(true); - return false; - } - + case 'c': /* config file */ confbase = xstrdup(optarg); break; - case 'D': /* no detach */ + case 'D': /* no detach */ do_detach = false; break; - case 'L': /* no detach */ + case 'L': /* no detach */ #ifndef HAVE_MLOCKALL - logger(LOG_ERR, "%s not supported on this platform", "mlockall()"); + logger(DEBUG_ALWAYS, LOG_ERR, "The %s option is not supported on this platform.", argv[optind - 1]); return false; #else do_mlock = true; break; #endif - case 'd': /* increase debug level */ + case 'd': /* increase debug level */ if(!optarg && optind < argc && *argv[optind] != '-') { optarg = argv[optind++]; } @@ -205,66 +184,16 @@ static bool parse_options(int argc, char **argv) { break; - case 'k': /* kill old tincds */ -#ifndef HAVE_MINGW - if(!optarg && optind < argc && *argv[optind] != '-') { - optarg = argv[optind++]; - } - - if(optarg) { - if(!strcasecmp(optarg, "HUP")) { - kill_tincd = SIGHUP; - } else if(!strcasecmp(optarg, "TERM")) { - kill_tincd = SIGTERM; - } else if(!strcasecmp(optarg, "KILL")) { - kill_tincd = SIGKILL; - } else if(!strcasecmp(optarg, "USR1")) { - kill_tincd = SIGUSR1; - } else if(!strcasecmp(optarg, "USR2")) { - kill_tincd = SIGUSR2; - } else if(!strcasecmp(optarg, "WINCH")) { - kill_tincd = SIGWINCH; - } else if(!strcasecmp(optarg, "INT")) { - kill_tincd = SIGINT; - } else if(!strcasecmp(optarg, "ALRM")) { - kill_tincd = SIGALRM; - } else if(!strcasecmp(optarg, "ABRT")) { - kill_tincd = SIGABRT; - } else { - kill_tincd = atoi(optarg); - - if(!kill_tincd) { - fprintf(stderr, "Invalid argument `%s'; SIGNAL must be a number or one of HUP, TERM, KILL, USR1, USR2, WINCH, INT or ALRM.\n", - optarg); - usage(true); - return false; - } - } - } else { - kill_tincd = SIGTERM; - } - -#else - kill_tincd = 1; -#endif + case 'n': /* net name given */ + netname = xstrdup(optarg); break; - case 'n': /* net name given */ - - /* netname "." is special: a "top-level name" */ - if(netname) { - fprintf(stderr, "Only one netname can be given.\n"); - usage(true); - return false; - } - - if(optarg && strcmp(optarg, ".")) { - netname = xstrdup(optarg); - } - + case 's': /* syslog */ + use_logfile = false; + use_syslog = true; break; - case 'o': /* option */ + case 'o': /* option */ cfg = parse_config_line(optarg, NULL, ++lineno); if(!cfg) { @@ -274,49 +203,37 @@ static bool parse_options(int argc, char **argv) { list_insert_tail(cmdline_conf, cfg); break; - case 'K': /* generate public/private keypair */ - if(!optarg && optind < argc && *argv[optind] != '-') { - optarg = argv[optind++]; - } +#ifdef HAVE_MINGW - if(optarg) { - generate_keys = atoi(optarg); + case 'R': + case 'U': + logger(DEBUG_ALWAYS, LOG_ERR, "The %s option is not supported on this platform.", argv[optind - 1]); + return false; +#else - if(generate_keys < 512) { - fprintf(stderr, "Invalid argument `%s'; BITS must be a number equal to or greater than 512.\n", - optarg); - usage(true); - return false; - } - - generate_keys &= ~7; /* Round it to bytes */ - } else { - generate_keys = 2048; - } - - break; - - case 'R': /* chroot to NETNAME dir */ + case 'R': /* chroot to NETNAME dir */ do_chroot = true; break; - case 'U': /* setuid to USER */ + case 'U': /* setuid to USER */ switchuser = optarg; break; +#endif - case 1: /* show help */ + case 1: /* show help */ show_help = true; break; - case 2: /* show version */ + case 2: /* show version */ show_version = true; break; - case 3: /* bypass security */ + case 3: /* bypass security */ bypass_security = true; break; - case 4: /* write log entries to a file */ + case 4: /* write log entries to a file */ + use_syslog = false; use_logfile = true; if(!optarg && optind < argc && *argv[optind] != '-') { @@ -324,28 +241,16 @@ static bool parse_options(int argc, char **argv) { } if(optarg) { - if(logfilename) { - fprintf(stderr, "Only one logfile can be given.\n"); - usage(true); - return false; - } - logfilename = xstrdup(optarg); } break; - case 5: /* write PID to a file */ - if(pidfilename) { - fprintf(stderr, "Only one pidfile can be given.\n"); - usage(true); - return false; - } - + case 5: /* open control socket here */ pidfilename = xstrdup(optarg); break; - case '?': + case '?': /* wrong options */ usage(true); return false; @@ -360,239 +265,38 @@ static bool parse_options(int argc, char **argv) { return false; } - return true; -} - -/* This function prettyprints the key generation process */ - -static int indicator(int a, int b, BN_GENCB *cb) { - (void)cb; - - switch(a) { - case 0: - fprintf(stderr, "."); - break; - - case 1: - fprintf(stderr, "+"); - break; - - case 2: - fprintf(stderr, "-"); - break; - - case 3: - switch(b) { - case 0: - fprintf(stderr, " p\n"); - break; - - case 1: - fprintf(stderr, " q\n"); - break; - - default: - fprintf(stderr, "?"); - } - - break; - - default: - fprintf(stderr, "?"); + if(!netname && (netname = getenv("NETNAME"))) { + netname = xstrdup(netname); } - return 1; -} + /* netname "." is special: a "top-level name" */ -#ifndef HAVE_BN_GENCB_NEW -BN_GENCB *BN_GENCB_new(void) { - return xmalloc_and_zero(sizeof(BN_GENCB)); -} - -void BN_GENCB_free(BN_GENCB *cb) { - free(cb); -} -#endif - -/* - Generate a public/private RSA keypair, and ask for a file to store - them in. -*/ -static bool keygen(int bits) { - BIGNUM *e = NULL; - RSA *rsa_key; - FILE *f; - char filename[PATH_MAX]; - BN_GENCB *cb; - int result; - - fprintf(stderr, "Generating %d bits keys:\n", bits); - - cb = BN_GENCB_new(); - - if(!cb) { - abort(); + if(netname && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; } - BN_GENCB_set(cb, indicator, NULL); - - rsa_key = RSA_new(); - - if(BN_hex2bn(&e, "10001") == 0) { - abort(); - } - - if(!rsa_key || !e) { - abort(); - } - - result = RSA_generate_key_ex(rsa_key, bits, e, cb); - - BN_free(e); - BN_GENCB_free(cb); - - if(!result) { - fprintf(stderr, "Error during key generation!\n"); - RSA_free(rsa_key); - return false; - } else { - fprintf(stderr, "Done.\n"); - } - - snprintf(filename, sizeof(filename), "%s/rsa_key.priv", confbase); - f = ask_and_open(filename, "private RSA key"); - - if(!f) { - RSA_free(rsa_key); + if(netname && !check_netname(netname, false)) { + fprintf(stderr, "Invalid character in netname!\n"); return false; } -#ifdef HAVE_FCHMOD - /* Make it unreadable for others. */ - fchmod(fileno(f), 0600); -#endif - - fputc('\n', f); - PEM_write_RSAPrivateKey(f, rsa_key, NULL, NULL, 0, NULL, NULL); - fclose(f); - - char *name = get_name(); - - if(name) { - snprintf(filename, sizeof(filename), "%s/hosts/%s", confbase, name); - free(name); - } else { - snprintf(filename, sizeof(filename), "%s/rsa_key.pub", confbase); + if(netname && !check_netname(netname, true)) { + fprintf(stderr, "Warning: unsafe character in netname!\n"); } - f = ask_and_open(filename, "public RSA key"); - - if(!f) { - RSA_free(rsa_key); - return false; - } - - fputc('\n', f); - PEM_write_RSAPublicKey(f, rsa_key); - fclose(f); - - RSA_free(rsa_key); - return true; } -/* - Set all files and paths according to netname -*/ -static void make_names(void) { -#ifdef HAVE_MINGW - HKEY key; - char installdir[1024] = ""; - DWORD len = sizeof(installdir); -#endif - - if(netname) { - xasprintf(&identname, "tinc.%s", netname); - } else { - identname = xstrdup("tinc"); - } - -#ifdef HAVE_MINGW - - if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\tinc", 0, KEY_READ, &key)) { - if(!RegQueryValueEx(key, NULL, 0, 0, (LPBYTE)installdir, &len)) { - if(!confbase) { - if(netname) { - xasprintf(&confbase, "%s/%s", installdir, netname); - } else { - xasprintf(&confbase, "%s", installdir); - } - } - - if(!logfilename) { - xasprintf(&logfilename, "%s/tinc.log", confbase); - } - } - - RegCloseKey(key); - - if(*installdir) { - return; - } - } - -#endif - - if(!pidfilename) { - xasprintf(&pidfilename, RUNSTATEDIR "/%s.pid", identname); - } - - if(!logfilename) { - xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname); - } - - if(netname) { - if(!confbase) { - xasprintf(&confbase, CONFDIR "/tinc/%s", netname); - } else { - logger(LOG_INFO, "Both netname and configuration directory given, using the latter..."); - } - } else { - if(!confbase) { - xasprintf(&confbase, CONFDIR "/tinc"); - } - } -} - -static void free_names() { - free(identname); - free(netname); - free(pidfilename); - free(logfilename); - free(confbase); -} - -static bool drop_privs() { -#ifdef HAVE_MINGW - - if(switchuser) { - logger(LOG_ERR, "%s not supported on this platform", "-U"); - return false; - } - - if(do_chroot) { - logger(LOG_ERR, "%s not supported on this platform", "-R"); - return false; - } - -#else +static bool drop_privs(void) { +#ifndef HAVE_MINGW uid_t uid = 0; if(switchuser) { struct passwd *pw = getpwnam(switchuser); if(!pw) { - logger(LOG_ERR, "unknown user `%s'", switchuser); + logger(DEBUG_ALWAYS, LOG_ERR, "unknown user `%s'", switchuser); return false; } @@ -600,12 +304,12 @@ static bool drop_privs() { if(initgroups(switchuser, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "initgroups", strerror(errno)); return false; } -#ifndef ANDROID +#ifndef __ANDROID__ // Not supported in android NDK endgrent(); endpwent(); @@ -616,7 +320,7 @@ static bool drop_privs() { tzset(); /* for proper timestamps in logs */ if(chroot(confbase) != 0 || chdir("/") != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "chroot", strerror(errno)); return false; } @@ -627,7 +331,7 @@ static bool drop_privs() { if(switchuser) if(setuid(uid) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setuid", strerror(errno)); return false; } @@ -638,6 +342,25 @@ static bool drop_privs() { #ifdef HAVE_MINGW # define setpriority(level) !SetPriorityClass(GetCurrentProcess(), (level)) + +static void stop_handler(void *data, int flags) { + (void)data; + (void)flags; + + event_exit(); +} + +static BOOL WINAPI console_ctrl_handler(DWORD type) { + (void)type; + + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got console shutdown request"); + + if(WSASetEvent(stop_io.event) == FALSE) { + abort(); + } + + return TRUE; +} #else # define NORMAL_PRIORITY_CLASS 0 # define BELOW_NORMAL_PRIORITY_CLASS 10 @@ -653,8 +376,9 @@ int main(int argc, char **argv) { } if(show_version) { - printf("%s version %s\n", PACKAGE, VERSION); - printf("Copyright (C) 1998-2019 Ivo Timmermans, Guus Sliepen and others.\n" + printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, + BUILD_VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR); + printf("Copyright (C) 1998-2021 Ivo Timmermans, Guus Sliepen and others.\n" "See the AUTHORS file for a complete list.\n\n" "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" "and you are welcome to redistribute it under certain conditions;\n" @@ -668,12 +392,38 @@ int main(int argc, char **argv) { return 0; } - make_names(); + make_names(true); + chdir(confbase); - if(kill_tincd) { - return !kill_other(kill_tincd); +#ifdef HAVE_MINGW + + if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); + return 1; } +#else + // Check if we got an umbilical fd from the process that started us + char *umbstr = getenv("TINC_UMBILICAL"); + + if(umbstr) { + umbilical = atoi(umbstr); + + if(fcntl(umbilical, F_GETFL) < 0) { + umbilical = 0; + } + +#ifdef FD_CLOEXEC + + if(umbilical) { + fcntl(umbilical, F_SETFD, FD_CLOEXEC); + } + +#endif + } + +#endif + openlogger("tinc", use_logfile ? LOGMODE_FILE : LOGMODE_STDERR); g_argv = argv; @@ -688,50 +438,56 @@ int main(int argc, char **argv) { init_configuration(&config_tree); -#ifndef OPENSSL_NO_ENGINE - ENGINE_load_builtin_engines(); - ENGINE_register_all_complete(); -#endif + /* Slllluuuuuuurrrrp! */ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - OpenSSL_add_all_algorithms(); -#endif - - if(generate_keys) { - read_server_config(); - return !keygen(generate_keys); - } + gettimeofday(&now, NULL); + srand(now.tv_sec + now.tv_usec); + crypto_init(); if(!read_server_config()) { return 1; } + if(!debug_level) { + get_config_int(lookup_config(config_tree, "LogLevel"), &debug_level); + } + #ifdef HAVE_LZO if(lzo_init() != LZO_E_OK) { - logger(LOG_ERR, "Error initializing LZO compressor!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error initializing LZO compressor!"); return 1; } #endif #ifdef HAVE_MINGW + io_add_event(&stop_io, stop_handler, NULL, WSACreateEvent()); - if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { - logger(LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); - return 1; + if(stop_io.event == FALSE) { + abort(); } + int result; + if(!do_detach || !init_service()) { - return main2(argc, argv); + SetConsoleCtrlHandler(console_ctrl_handler, TRUE); + result = main2(argc, argv); } else { - return 1; + result = 1; } + + if(WSACloseEvent(stop_io.event) == FALSE) { + abort(); + } + + io_del(&stop_io); + return result; } int main2(int argc, char **argv) { - InitializeCriticalSection(&mutex); - EnterCriticalSection(&mutex); + (void)argc; + (void)argv; #endif char *priority = NULL; @@ -745,7 +501,7 @@ int main2(int argc, char **argv) { * This has to be done after daemon()/fork() so it works for child. * No need to do that in parent as it's very short-lived. */ if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "mlockall", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "mlockall", strerror(errno)); return 1; } @@ -758,33 +514,26 @@ int main2(int argc, char **argv) { goto end; } - /* Initiate all outgoing connections. */ - - try_outgoing_connections(); - /* Change process priority */ if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) { if(!strcasecmp(priority, "Normal")) { if(setpriority(NORMAL_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); goto end; } } else if(!strcasecmp(priority, "Low")) { if(setpriority(BELOW_NORMAL_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); goto end; } } else if(!strcasecmp(priority, "High")) { if(setpriority(HIGH_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); goto end; } } else { - logger(LOG_ERR, "Invalid priority `%s`!", priority); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid priority `%s`!", priority); goto end; } } @@ -796,34 +545,31 @@ int main2(int argc, char **argv) { /* Start main loop. It only exits when tinc is killed. */ + logger(DEBUG_ALWAYS, LOG_NOTICE, "Ready"); + + if(umbilical) { // snip! + write(umbilical, "", 1); + close(umbilical); + umbilical = 0; + } + + try_outgoing_connections(); + status = main_loop(); /* Shutdown properly. */ - ifdebug(CONNECTIONS) - devops.dump_stats(); - +end: close_network_connections(); -end: - logger(LOG_NOTICE, "Terminating"); - -#ifndef HAVE_MINGW - remove_pid(pidfilename); -#endif + logger(DEBUG_ALWAYS, LOG_NOTICE, "Terminating"); free(priority); -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_cleanup(); - ERR_free_strings(); -#ifndef OPENSSL_NO_ENGINE - ENGINE_cleanup(); -#endif -#endif + crypto_exit(); exit_configuration(&config_tree); - list_delete_list(cmdline_conf); + free(cmdline_conf); free_names(); return status; diff --git a/src/top.c b/src/top.c new file mode 100644 index 0000000..ab14619 --- /dev/null +++ b/src/top.c @@ -0,0 +1,396 @@ +/* + top.c -- Show real-time statistics from a running tincd + Copyright (C) 2011-2013 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" + +#ifdef HAVE_CURSES + +#undef KEY_EVENT /* There are conflicting declarations for KEY_EVENT in Windows wincon.h and curses.h. */ +#include + +#include "control_common.h" +#include "list.h" +#include "names.h" +#include "tincctl.h" +#include "top.h" +#include "xalloc.h" + +typedef struct nodestats_t { + char *name; + int i; + uint64_t in_packets; + uint64_t in_bytes; + uint64_t out_packets; + uint64_t out_bytes; + float in_packets_rate; + float in_bytes_rate; + float out_packets_rate; + float out_bytes_rate; + bool known; +} nodestats_t; + +static const char *const sortname[] = { + "name", + "in pkts", + "in bytes", + "out pkts", + "out bytes", + "tot pkts", + "tot bytes", +}; + +static int sortmode = 0; +static bool cumulative = false; + +static list_t node_list; +static struct timeval cur, prev, diff; +static int delay = 1000; +static bool changed = true; +static const char *bunit = "bytes"; +static float bscale = 1; +static const char *punit = "pkts"; +static float pscale = 1; + +static bool update(int fd) { + if(!sendline(fd, "%d %d", CONTROL, REQ_DUMP_TRAFFIC)) { + return false; + } + + gettimeofday(&cur, NULL); + + timersub(&cur, &prev, &diff); + prev = cur; + float interval = diff.tv_sec + diff.tv_usec * 1e-6; + + char line[4096]; + char name[4096]; + int code; + int req; + uint64_t in_packets; + uint64_t in_bytes; + uint64_t out_packets; + uint64_t out_bytes; + + for list_each(nodestats_t, ns, &node_list) { + ns->known = false; + } + + while(recvline(fd, line, sizeof(line))) { + int n = sscanf(line, "%d %d %4095s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, &code, &req, name, &in_packets, &in_bytes, &out_packets, &out_bytes); + + if(n == 2) { + return true; + } + + if(n != 7) { + return false; + } + + nodestats_t *found = NULL; + + for list_each(nodestats_t, ns, &node_list) { + int result = strcmp(name, ns->name); + + if(result > 0) { + continue; + } + + if(result == 0) { + found = ns; + break; + } else { + found = xzalloc(sizeof(*found)); + found->name = xstrdup(name); + list_insert_before(&node_list, node, found); + changed = true; + break; + } + } + + if(!found) { + found = xzalloc(sizeof(*found)); + found->name = xstrdup(name); + list_insert_tail(&node_list, found); + changed = true; + } + + found->known = true; + found->in_packets_rate = (in_packets - found->in_packets) / interval; + found->in_bytes_rate = (in_bytes - found->in_bytes) / interval; + found->out_packets_rate = (out_packets - found->out_packets) / interval; + found->out_bytes_rate = (out_bytes - found->out_bytes) / interval; + found->in_packets = in_packets; + found->in_bytes = in_bytes; + found->out_packets = out_packets; + found->out_bytes = out_bytes; + } + + return false; +} + +static int cmpfloat(float a, float b) { + if(a < b) { + return -1; + } else if(a > b) { + return 1; + } else { + return 0; + } +} + +static int cmpu64(uint64_t a, uint64_t b) { + if(a < b) { + return -1; + } else if(a > b) { + return 1; + } else { + return 0; + } +} + +static int sortfunc(const void *a, const void *b) { + const nodestats_t *na = *(const nodestats_t **)a; + const nodestats_t *nb = *(const nodestats_t **)b; + int result; + + switch(sortmode) { + case 1: + if(cumulative) { + result = -cmpu64(na->in_packets, nb->in_packets); + } else { + result = -cmpfloat(na->in_packets_rate, nb->in_packets_rate); + } + + break; + + case 2: + if(cumulative) { + result = -cmpu64(na->in_bytes, nb->in_bytes); + } else { + result = -cmpfloat(na->in_bytes_rate, nb->in_bytes_rate); + } + + break; + + case 3: + if(cumulative) { + result = -cmpu64(na->out_packets, nb->out_packets); + } else { + result = -cmpfloat(na->out_packets_rate, nb->out_packets_rate); + } + + break; + + case 4: + if(cumulative) { + result = -cmpu64(na->out_bytes, nb->out_bytes); + } else { + result = -cmpfloat(na->out_bytes_rate, nb->out_bytes_rate); + } + + break; + + case 5: + if(cumulative) { + result = -cmpu64(na->in_packets + na->out_packets, nb->in_packets + nb->out_packets); + } else { + result = -cmpfloat(na->in_packets_rate + na->out_packets_rate, nb->in_packets_rate + nb->out_packets_rate); + } + + break; + + case 6: + if(cumulative) { + result = -cmpu64(na->in_bytes + na->out_bytes, nb->in_bytes + nb->out_bytes); + } else { + result = -cmpfloat(na->in_bytes_rate + na->out_bytes_rate, nb->in_bytes_rate + nb->out_bytes_rate); + } + + break; + + default: + result = strcmp(na->name, nb->name); + break; + } + + if(result) { + return result; + } else { + return na->i - nb->i; + } +} + +static void redraw(void) { + erase(); + + mvprintw(0, 0, "Tinc %-16s Nodes: %4d Sort: %-10s %s", netname ? netname : "", node_list.count, sortname[sortmode], cumulative ? "Cumulative" : "Current"); + attrset(A_REVERSE); + mvprintw(2, 0, "Node IN %s IN %s OUT %s OUT %s", punit, bunit, punit, bunit); + chgat(-1, A_REVERSE, 0, NULL); + + static nodestats_t **sorted = 0; + static int n = 0; + + if(changed) { + n = 0; + sorted = xrealloc(sorted, node_list.count * sizeof(*sorted)); + + for list_each(nodestats_t, ns, &node_list) { + sorted[n++] = ns; + } + + changed = false; + } + + for(int i = 0; i < n; i++) { + sorted[i]->i = i; + } + + if(sorted) { + qsort(sorted, n, sizeof(*sorted), sortfunc); + } + + for(int i = 0, row = 3; i < n; i++, row++) { + nodestats_t *node = sorted[i]; + + if(node->known) + if(node->in_packets_rate || node->out_packets_rate) { + attrset(A_BOLD); + } else { + attrset(A_NORMAL); + } else { + attrset(A_DIM); + } + + if(cumulative) + mvprintw(row, 0, "%-16s %10.0f %10.0f %10.0f %10.0f", + node->name, node->in_packets * pscale, node->in_bytes * bscale, node->out_packets * pscale, node->out_bytes * bscale); + else + mvprintw(row, 0, "%-16s %10.0f %10.0f %10.0f %10.0f", + node->name, node->in_packets_rate * pscale, node->in_bytes_rate * bscale, node->out_packets_rate * pscale, node->out_bytes_rate * bscale); + } + + attrset(A_NORMAL); + move(1, 0); + + refresh(); +} + +void top(int fd) { + initscr(); + timeout(delay); + bool running = true; + + while(running) { + if(!update(fd)) { + break; + } + + redraw(); + + switch(getch()) { + case 's': { + timeout(-1); + float input = delay * 1e-3; + mvprintw(1, 0, "Change delay from %.1fs to: ", input); + scanw("%f", &input); + + if(input < 0.1) { + input = 0.1; + } + + delay = input * 1e3; + timeout(delay); + break; + } + + case 'c': + cumulative = !cumulative; + break; + + case 'n': + sortmode = 0; + break; + + case 'i': + sortmode = 2; + break; + + case 'I': + sortmode = 1; + break; + + case 'o': + sortmode = 4; + break; + + case 'O': + sortmode = 3; + break; + + case 't': + sortmode = 6; + break; + + case 'T': + sortmode = 5; + break; + + case 'b': + bunit = "bytes"; + bscale = 1; + punit = "pkts"; + pscale = 1; + break; + + case 'k': + bunit = "kbyte"; + bscale = 1e-3; + punit = "pkts"; + pscale = 1; + break; + + case 'M': + bunit = "Mbyte"; + bscale = 1e-6; + punit = "kpkt"; + pscale = 1e-3; + break; + + case 'G': + bunit = "Gbyte"; + bscale = 1e-9; + punit = "Mpkt"; + pscale = 1e-6; + break; + + case 'q': + case KEY_BREAK: + running = false; + break; + + default: + break; + } + } + + endwin(); +} + +#endif diff --git a/src/top.h b/src/top.h new file mode 100644 index 0000000..612d0d8 --- /dev/null +++ b/src/top.h @@ -0,0 +1,25 @@ +#ifndef TINC_TOP_H +#define TINC_TOP_H + +/* + top.h -- header for top.c. + Copyright (C) 2011 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. +*/ + +extern void top(int fd); + +#endif diff --git a/src/uml_device.c b/src/uml_device.c index 66de431..a675b62 100644 --- a/src/uml_device.c +++ b/src/uml_device.c @@ -1,7 +1,7 @@ /* device.c -- UML network socket Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2012 Guus Sliepen + 2002-2017 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 @@ -24,6 +24,7 @@ #include "conf.h" #include "device.h" +#include "names.h" #include "net.h" #include "logger.h" #include "utils.h" @@ -37,12 +38,6 @@ static int write_fd = -1; static int state = 0; static const char *device_info = "UML network socket"; -extern char *identname; -extern volatile bool running; - -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - enum request_type { REQ_NEW_CONTROL }; static struct request { @@ -71,8 +66,8 @@ static bool setup_device(void) { get_config_string(lookup_config(config_tree, "Interface"), &iface); if((write_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { - logger(LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno)); + event_exit(); return false; } @@ -83,14 +78,14 @@ static bool setup_device(void) { setsockopt(write_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(fcntl(write_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); - running = false; + 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(LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno)); + event_exit(); return false; } @@ -101,8 +96,8 @@ static bool setup_device(void) { setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + event_exit(); return false; } @@ -114,13 +109,13 @@ static bool setup_device(void) { memcpy(&data_sun.sun_path, &name, sizeof(name)); if(bind(data_fd, (struct sockaddr *)&data_sun, sizeof(data_sun)) < 0) { - logger(LOG_ERR, "Could not bind data %s: %s", device_info, strerror(errno)); - running = false; + 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(LOG_ERR, "Could not open %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info, strerror(errno)); return false; } @@ -132,27 +127,28 @@ static bool setup_device(void) { setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); return false; } listen_sun.sun_family = AF_UNIX; strncpy(listen_sun.sun_path, device, sizeof(listen_sun.sun_path)); + listen_sun.sun_path[sizeof(listen_sun.sun_path) - 1] = 0; if(bind(listen_fd, (struct sockaddr *)&listen_sun, sizeof(listen_sun)) < 0) { - logger(LOG_ERR, "Could not bind %s to %s: %s", device_info, device, strerror(errno)); + 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(LOG_ERR, "Could not listen on %s %s: %s", device_info, device, strerror(errno)); + 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(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); if(routing_mode == RMODE_ROUTER) { overwrite_mac = true; @@ -164,28 +160,37 @@ static bool setup_device(void) { 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) { - int lenin; + int inlen; switch(state) { case 0: { @@ -195,7 +200,7 @@ static bool read_packet(vpn_packet_t *packet) { request_fd = accept(listen_fd, &sa, &salen); if(request_fd < 0) { - logger(LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -204,8 +209,8 @@ static bool read_packet(vpn_packet_t *packet) { #endif if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + event_exit(); return false; } @@ -218,93 +223,82 @@ static bool read_packet(vpn_packet_t *packet) { } case 1: { - if((lenin = read(request_fd, &request, sizeof(request))) != sizeof request) { - logger(LOG_ERR, "Error while reading request from %s %s: %s", device_info, + 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)); - running = false; + event_exit(); return false; } if(request.magic != 0xfeedface || request.version != 3 || request.type != REQ_NEW_CONTROL) { - logger(LOG_ERR, "Unknown magic %x, version %d, request type %d from %s %s", + 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); - running = false; + event_exit(); return false; } - if(connect(write_fd, (struct sockaddr *)&request.sock, sizeof(request.sock)) < 0) { - logger(LOG_ERR, "Could not bind write %s: %s", device_info, strerror(errno)); - running = 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; } write(request_fd, &data_sun, sizeof(data_sun)); device_fd = data_fd; - logger(LOG_INFO, "Connection with UML established"); + logger(DEBUG_ALWAYS, LOG_INFO, "Connection with UML established"); state = 2; return false; } case 2: { - if((lenin = read(data_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + 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)); - running = false; + event_exit(); return false; } - packet->len = lenin; + packet->len = inlen; - device_total_in += packet->len; - - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, - device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); return true; } default: - logger(LOG_ERR, "Invalid value for state variable in " __FILE__); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid value for state variable in " __FILE__); abort(); } } static bool write_packet(vpn_packet_t *packet) { if(state != 2) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet", + packet->len, device_info); return false; } - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", - packet->len, device_info); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); - if(write(write_fd, packet->data, packet->len) < 0) { + if(write(write_fd, DATA(packet), packet->len) < 0) { if(errno != EINTR && errno != EAGAIN) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + event_exit(); } return false; } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t uml_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/upnp.c b/src/upnp.c new file mode 100644 index 0000000..553630e --- /dev/null +++ b/src/upnp.c @@ -0,0 +1,199 @@ +/* + upnp.c -- UPnP-IGD client + Copyright (C) 2015-2018 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 "upnp.h" + +#ifndef HAVE_MINGW +#include +#endif + +#include "miniupnpc/miniupnpc.h" +#include "miniupnpc/upnpcommands.h" +#include "miniupnpc/upnperrors.h" + +#include "system.h" +#include "logger.h" +#include "names.h" +#include "net.h" +#include "netutl.h" +#include "utils.h" + +static bool upnp_tcp; +static bool upnp_udp; +static int upnp_discover_wait = 5; +static int upnp_refresh_period = 60; + +// Unfortunately, libminiupnpc devs don't seem to care about API compatibility, +// and there are slight changes to function signatures between library versions. +// Well, at least they publish a "MINIUPNPC_API_VERSION" constant, so we got that going for us, which is nice. +// Differences between API versions are documented in "apiversions.txt" in the libminiupnpc distribution. + +#ifndef MINIUPNPC_API_VERSION +#define MINIUPNPC_API_VERSION 0 +#endif + +static struct UPNPDev *upnp_discover(int delay, int *error) { +#if MINIUPNPC_API_VERSION <= 13 + +#if MINIUPNPC_API_VERSION < 8 +#warning "The version of libminiupnpc you're building against seems to be too old. Expect trouble." +#endif + + return upnpDiscover(delay, NULL, NULL, false, false, error); + +#elif MINIUPNPC_API_VERSION <= 14 + + return upnpDiscover(delay, NULL, NULL, false, false, 2, error); + +#else + +#if MINIUPNPC_API_VERSION > 17 +#warning "The version of libminiupnpc you're building against seems to be too recent. Expect trouble." +#endif + + return upnpDiscover(delay, NULL, NULL, UPNP_LOCAL_PORT_ANY, false, 2, error); + +#endif +} + +static void upnp_add_mapping(struct UPNPUrls *urls, struct IGDdatas *data, const char *myaddr, int socket, const char *proto) { + // Extract the port from the listening socket. + // Note that we can't simply use listen_socket[].sa because this won't have the port + // if we're running with Port=0 (dynamically assigned port). + sockaddr_t sa; + socklen_t salen = sizeof(sa); + + if(getsockname(socket, &sa.sa, &salen)) { + logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket address: [%d] %s", sockerrno, sockstrerror(sockerrno)); + return; + } + + char *port; + sockaddr2str(&sa, NULL, &port); + + if(!port) { + logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket port"); + return; + } + + // Use a lease twice as long as the refresh period so that the mapping won't expire before we refresh. + char lease_duration[16]; + snprintf(lease_duration, sizeof(lease_duration), "%d", upnp_refresh_period * 2); + + int error = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, port, port, myaddr, identname, proto, NULL, lease_duration); + + if(error == 0) { + logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Successfully set port mapping (%s:%s %s for %s seconds)", myaddr, port, proto, lease_duration); + } else { + logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Failed to set port mapping (%s:%s %s for %s seconds): [%d] %s", myaddr, port, proto, lease_duration, error, strupnperror(error)); + } + + free(port); +} + +static void upnp_refresh() { + logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Discovering IGD devices"); + + int error; + struct UPNPDev *devices = upnp_discover(upnp_discover_wait * 1000, &error); + + if(!devices) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] Unable to find IGD devices: [%d] %s", error, strupnperror(error)); + freeUPNPDevlist(devices); + return; + } + + struct UPNPUrls urls; + + struct IGDdatas data; + + char myaddr[64]; + + int result = UPNP_GetValidIGD(devices, &urls, &data, myaddr, sizeof(myaddr)); + + if(result <= 0) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] No IGD found"); + freeUPNPDevlist(devices); + return; + } + + logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] IGD found: [%d] %s (local address: %s, service type: %s)", result, urls.controlURL, myaddr, data.first.servicetype); + + for(int i = 0; i < listen_sockets; i++) { + if(upnp_tcp) { + upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].tcp.fd, "TCP"); + } + + if(upnp_udp) { + upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].udp.fd, "UDP"); + } + } + + FreeUPNPUrls(&urls); + freeUPNPDevlist(devices); +} + +static void *upnp_thread(void *data) { + (void)data; + + while(true) { + time_t start = time(NULL); + upnp_refresh(); + + // Make sure we'll stick to the refresh period no matter how long upnp_refresh() takes. + time_t refresh_time = start + upnp_refresh_period; + time_t now = time(NULL); + + if(now < refresh_time) { + nanosleep(&(struct timespec) { + refresh_time - now, 0 + }, NULL); + } + } + + // TODO: we don't have a clean thread shutdown procedure, so we can't remove the mapping. + // this is probably not a concern as long as the UPnP device honors the lease duration, + // but considering how bug-riddled these devices often are, that's a big "if". + return NULL; +} + +void upnp_init(bool tcp, bool udp) { + upnp_tcp = tcp; + upnp_udp = udp; + + get_config_int(lookup_config(config_tree, "UPnPDiscoverWait"), &upnp_discover_wait); + get_config_int(lookup_config(config_tree, "UPnPRefreshPeriod"), &upnp_refresh_period); + +#ifdef HAVE_MINGW + HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)upnp_thread, NULL, 0, NULL); + + if(!handle) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread"); + } + +#else + pthread_t thread; + int error = pthread_create(&thread, NULL, upnp_thread, NULL); + + if(error) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread: [%d] %s", error, strerror(error)); + } + +#endif +} diff --git a/src/proxy.h b/src/upnp.h similarity index 62% rename from src/proxy.h rename to src/upnp.h index a96fc3d..b611bc1 100644 --- a/src/proxy.h +++ b/src/upnp.h @@ -1,8 +1,8 @@ -#ifndef TINC_PROXY_H -#define TINC_PROXY_H +#ifndef TINC_UPNP_H +#define TINC_UPNP_H /* - proxy.h -- header for proxy.c + upnp.h -- UPnP-IGD client Copyright (C) 2015 Guus Sliepen This program is free software; you can redistribute it and/or modify @@ -20,24 +20,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "connection.h" +#include "system.h" -typedef enum proxytype_t { - PROXY_NONE = 0, - PROXY_SOCKS4, - PROXY_SOCKS4A, - PROXY_SOCKS5, - PROXY_HTTP, - PROXY_EXEC, -} proxytype_t; - -extern proxytype_t proxytype; -extern char *proxyhost; -extern char *proxyport; -extern char *proxyuser; -extern char *proxypass; - -extern bool send_proxyrequest(struct connection_t *c); -extern int receive_proxy_meta(struct connection_t *c); +extern void upnp_init(bool tcp, bool udp); #endif diff --git a/src/utils.c b/src/utils.c index 70a5c99..c9f3085 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,7 +1,7 @@ /* utils.c -- gathering of some stupid small functions Copyright (C) 1999-2005 Ivo Timmermans - 2000-2014 Guus Sliepen + 2000-2013 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 @@ -18,12 +18,32 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "logger.h" #include "system.h" - -#include "../src/logger.h" #include "utils.h" +#include "xalloc.h" static const char hexadecimals[] = "0123456789ABCDEF"; +static const char base64_original[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char base64_urlsafe[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static const char base64_decode[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; static int charhex2bin(char c) { if(isdigit(c)) { @@ -33,40 +53,135 @@ static int charhex2bin(char c) { } } -bool hex2bin(char *src, char *dst, int length) { - for(int i = 0; i < length; i++) { - if(!isxdigit(src[i * 2]) || !isxdigit(src[i * 2 + 1])) { - return false; - } +size_t hex2bin(const char *src, void *vdst, size_t length) { + char *dst = vdst; + size_t i; + for(i = 0; i < length && isxdigit(src[i * 2]) && isxdigit(src[i * 2 + 1]); i++) { dst[i] = charhex2bin(src[i * 2]) * 16 + charhex2bin(src[i * 2 + 1]); } - return true; + return i; } -void bin2hex(char *src, char *dst, int length) { - int i; +size_t bin2hex(const void *vsrc, char *dst, size_t length) { + const char *src = vsrc; - for(i = length - 1; i >= 0; i--) { + for(size_t i = length; i-- > 0;) { dst[i * 2 + 1] = hexadecimals[(unsigned char) src[i] & 15]; dst[i * 2] = hexadecimals[(unsigned char) src[i] >> 4]; } + + dst[length * 2] = 0; + return length * 2; } -#if defined(HAVE_MINGW) || defined(HAVE_CYGWIN) -#ifdef HAVE_CYGWIN -#include -#endif +size_t b64decode(const char *src, void *dst, size_t length) { + size_t i; + uint32_t triplet = 0; + unsigned char *udst = (unsigned char *)dst; + for(i = 0; i < length && src[i]; i++) { + triplet |= base64_decode[src[i] & 0xff] << (6 * (i & 3)); + + if((i & 3) == 3) { + if(triplet & 0xff000000U) { + return 0; + } + + udst[0] = triplet & 0xff; + triplet >>= 8; + udst[1] = triplet & 0xff; + triplet >>= 8; + udst[2] = triplet; + triplet = 0; + udst += 3; + } + } + + if(triplet & 0xff000000U) { + return 0; + } + + if((i & 3) == 3) { + udst[0] = triplet & 0xff; + triplet >>= 8; + udst[1] = triplet & 0xff; + return i / 4 * 3 + 2; + } else if((i & 3) == 2) { + udst[0] = triplet & 0xff; + return i / 4 * 3 + 1; + } else { + return i / 4 * 3; + } +} + +static size_t b64encode_internal(const void *src, char *dst, size_t length, const char *alphabet) { + uint32_t triplet; + const unsigned char *usrc = (unsigned char *)src; + size_t si = length / 3 * 3; + size_t di = length / 3 * 4; + + switch(length % 3) { + case 2: + triplet = usrc[si] | usrc[si + 1] << 8; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 2] = alphabet[triplet]; + dst[di + 3] = 0; + length = di + 3; + break; + + case 1: + triplet = usrc[si]; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet]; + dst[di + 2] = 0; + length = di + 2; + break; + + default: + dst[di] = 0; + length = di; + break; + } + + while(si > 0) { + di -= 4; + si -= 3; + triplet = usrc[si] | usrc[si + 1] << 8 | usrc[si + 2] << 16; + dst[di] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 1] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 2] = alphabet[triplet & 63]; + triplet >>= 6; + dst[di + 3] = alphabet[triplet]; + } + + return length; +} + +size_t b64encode(const void *src, char *dst, size_t length) { + return b64encode_internal(src, dst, length, base64_original); +} + +size_t b64encode_urlsafe(const void *src, char *dst, size_t length) { + return b64encode_internal(src, dst, length, base64_urlsafe); +} + +#ifdef HAVE_MINGW const char *winerror(int err) { static char buf[1024], *ptr; - ptr = buf + sprintf(buf, "(%d) ", err); + ptr = buf + snprintf(buf, sizeof(buf), "(%d) ", err); if(!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), ptr, sizeof(buf) - (ptr - buf), NULL)) { - strcpy(ptr, "(unable to format errormessage)"); + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ptr, sizeof(buf) - (ptr - buf), NULL)) { + strncpy(buf, "(unable to format errormessage)", sizeof(buf)); }; if((ptr = strchr(buf, '\r'))) { @@ -88,18 +203,83 @@ unsigned int bitfield_to_int(const void *bitfield, size_t size) { return value; } -/** - * As memcmp(), but constant-time. - * Returns 0 when data is equal, non-zero otherwise. - */ -int memcmp_constant_time(const void *a, const void *b, size_t size) { - const uint8_t *a1 = a, *b1 = b; - int ret = 0; - size_t i; - - for(i = 0; i < size; i++) { - ret |= *a1++ ^ *b1++; +bool check_id(const char *id) { + if(!id || !*id) { + return false; } - return ret; + for(; *id; id++) + if(!isalnum(*id) && *id != '_') { + return false; + } + + return true; +} + +bool check_netname(const char *netname, bool strict) { + if(!netname || !*netname || *netname == '.') { + return false; + } + + for(const char *c = netname; *c; c++) { + if(iscntrl(*c)) { + return false; + } + + if(*c == '/' || *c == '\\') { + return false; + } + + if(strict && strchr(" $%<>:`\"|?*", *c)) { + return false; + } + } + + return true; +} + +/* Windows doesn't define HOST_NAME_MAX. */ +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 255 +#endif + +char *replace_name(const char *name) { + char *ret_name; + + if(name[0] == '$') { + char *envname = getenv(name + 1); + char hostname[HOST_NAME_MAX + 1]; + + if(!envname) { + if(strcmp(name + 1, "HOST")) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid Name: environment variable %s does not exist\n", name + 1); + return NULL; + } + + if(gethostname(hostname, sizeof(hostname)) || !*hostname) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get hostname: %s\n", sockstrerror(sockerrno)); + return NULL; + } + + hostname[HOST_NAME_MAX] = 0; + envname = hostname; + } + + ret_name = xstrdup(envname); + + for(char *c = ret_name; *c; c++) + if(!isalnum(*c)) { + *c = '_'; + } + } else { + ret_name = xstrdup(name); + } + + if(!check_id(ret_name)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!"); + free(ret_name); + return NULL; + } + + return ret_name; } diff --git a/src/utils.h b/src/utils.h index 7952025..4285150 100644 --- a/src/utils.h +++ b/src/utils.h @@ -4,7 +4,7 @@ /* utils.h -- header file for utils.c Copyright (C) 1999-2005 Ivo Timmermans - 2000-2014 Guus Sliepen + 2000-2013 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 @@ -21,30 +21,37 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -extern bool hex2bin(char *src, char *dst, int length); -extern void bin2hex(char *src, char *dst, int length); +extern size_t hex2bin(const char *src, void *dst, size_t length); +extern size_t bin2hex(const void *src, char *dst, size_t length); -#if defined(HAVE_MINGW) || defined(HAVE_CYGWIN) -extern const char *winerror(int); -#endif +extern size_t b64encode(const void *src, char *dst, size_t length); +extern size_t b64encode_urlsafe(const void *src, char *dst, size_t length); +extern size_t b64decode(const char *src, void *dst, size_t length); #ifdef HAVE_MINGW +extern const char *winerror(int); #define strerror(x) ((x)>0?strerror(x):winerror(GetLastError())) #define sockerrno WSAGetLastError() #define sockstrerror(x) winerror(x) #define sockwouldblock(x) ((x) == WSAEWOULDBLOCK || (x) == WSAEINTR) #define sockmsgsize(x) ((x) == WSAEMSGSIZE) #define sockinprogress(x) ((x) == WSAEINPROGRESS || (x) == WSAEWOULDBLOCK) +#define sockinuse(x) ((x) == WSAEADDRINUSE) +#define socknotconn(x) ((x) == WSAENOTCONN) #else #define sockerrno errno #define sockstrerror(x) strerror(x) #define sockwouldblock(x) ((x) == EWOULDBLOCK || (x) == EINTR) #define sockmsgsize(x) ((x) == EMSGSIZE) #define sockinprogress(x) ((x) == EINPROGRESS) +#define sockinuse(x) ((x) == EADDRINUSE) +#define socknotconn(x) ((x) == ENOTCONN) #endif extern unsigned int bitfield_to_int(const void *bitfield, size_t size); -int memcmp_constant_time(const void *a, const void *b, size_t size); +extern bool check_id(const char *id); +extern bool check_netname(const char *netname, bool strict); +char *replace_name(const char *name); #endif diff --git a/src/vde_device.c b/src/vde_device.c index 6d854a6..0170af3 100644 --- a/src/vde_device.c +++ b/src/vde_device.c @@ -1,6 +1,6 @@ /* device.c -- VDE plug - Copyright (C) 2012 Guus Sliepen + Copyright (C) 2013 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 @@ -23,6 +23,7 @@ #include "conf.h" #include "device.h" +#include "names.h" #include "net.h" #include "logger.h" #include "utils.h" @@ -35,17 +36,11 @@ static int port = 0; static char *group = NULL; static const char *device_info = "VDE socket"; -extern char *identname; -extern volatile bool running; - -static uint64_t device_total_in = 0; -static uint64_t device_total_out = 0; - static bool setup_device(void) { libvdeplug_dynopen(plug); if(!plug.dl_handle) { - logger(LOG_ERR, "Could not open libvdeplug library!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open libvdeplug library!"); return false; } @@ -68,7 +63,7 @@ static bool setup_device(void) { conn = plug.vde_open(device, identname, &args); if(!conn) { - logger(LOG_ERR, "Could not open VDE socket %s", device); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open VDE socket %s", device); return false; } @@ -78,7 +73,7 @@ static bool setup_device(void) { fcntl(device_fd, F_SETFD, FD_CLOEXEC); #endif - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); if(routing_mode == RMODE_ROUTER) { overwrite_mac = true; @@ -90,6 +85,7 @@ static bool setup_device(void) { static void close_device(void) { if(conn) { plug.vde_close(conn); + conn = NULL; } if(plug.dl_handle) { @@ -97,51 +93,46 @@ static void close_device(void) { } free(device); + device = NULL; free(iface); + iface = NULL; + + device_info = NULL; } static bool read_packet(vpn_packet_t *packet) { - int lenin = (ssize_t)plug.vde_recv(conn, packet->data, MTU, 0); + int lenin = (ssize_t)plug.vde_recv(conn, DATA(packet), MTU, 0); if(lenin <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); + event_exit(); return false; } packet->len = lenin; - device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); + + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } static bool write_packet(vpn_packet_t *packet) { - if((ssize_t)plug.vde_send(conn, packet->data, packet->len, 0) < 0) { + if((ssize_t)plug.vde_send(conn, DATA(packet), packet->len, 0) < 0) { if(errno != EINTR && errno != EAGAIN) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); - running = false; + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + event_exit(); } return false; } - device_total_out += packet->len; - return true; } -static void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); -} - const devops_t vde_devops = { .setup = setup_device, .close = close_device, .read = read_packet, .write = write_packet, - .dump_stats = dump_device_stats, }; diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..d0af1cc --- /dev/null +++ b/src/version.c @@ -0,0 +1,31 @@ +/* + version.c -- version information + Copyright (C) 2014 Etienne Dechamps + + 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 "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__; +const char *const BUILD_TIME = __TIME__; +#ifdef GIT_DESCRIPTION +const char *const BUILD_VERSION = GIT_DESCRIPTION; +#else +const char *const BUILD_VERSION = VERSION; +#endif diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..a4cf0a7 --- /dev/null +++ b/src/version.h @@ -0,0 +1,27 @@ +#ifndef TINC_VERSION_H +#define TINC_VERSION_H + +/* + version.h -- header for version.c + Copyright (C) 2014 Etienne Dechamps + + 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. +*/ + +extern const char *const BUILD_DATE; +extern const char *const BUILD_TIME; +extern const char *const BUILD_VERSION; + +#endif diff --git a/src/xalloc.h b/src/xalloc.h index cda0871..34f02d0 100644 --- a/src/xalloc.h +++ b/src/xalloc.h @@ -4,7 +4,7 @@ /* xalloc.h -- malloc and related functions with out of memory checking Copyright (C) 1990, 91, 92, 93, 94, 95, 96, 97 Free Software Foundation, Inc. - Copyright (C) 2011-2017 Guus Sliepen + Copyright (C) 2011-2013 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 @@ -32,8 +32,8 @@ static inline void *xmalloc(size_t n) { return p; } -static inline void *xmalloc_and_zero(size_t n) __attribute__((__malloc__)); -static inline void *xmalloc_and_zero(size_t n) { +static inline void *xzalloc(size_t n) __attribute__((__malloc__)); +static inline void *xzalloc(size_t n) { void *p = calloc(1, n); if(!p) { @@ -53,7 +53,7 @@ static inline void *xrealloc(void *p, size_t n) { return p; } -static inline char *xstrdup(const char *s) __attribute__((__malloc__)); +static inline char *xstrdup(const char *s) __attribute__((__malloc__)) __attribute((__nonnull__)); static inline char *xstrdup(const char *s) { char *p = strdup(s); diff --git a/systemd/Makefile.in b/systemd/Makefile.in index e27e947..1759d66 100644 --- a/systemd/Makefile.in +++ b/systemd/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.16.2 from Makefile.am. +# Makefile.in generated by automake 1.16.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994-2020 Free Software Foundation, Inc. @@ -95,9 +95,12 @@ am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ $(top_srcdir)/m4/ax_check_compile_flag.m4 \ $(top_srcdir)/m4/ax_check_link_flag.m4 \ - $(top_srcdir)/m4/ax_require_defined.m4 $(top_srcdir)/m4/lzo.m4 \ - $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/zlib.m4 \ - $(top_srcdir)/configure.ac + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) @@ -166,8 +169,15 @@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ @@ -176,17 +186,21 @@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ MKDIR_P = @MKDIR_P@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ @@ -197,6 +211,8 @@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ diff --git a/systemd/tinc@.service.in b/systemd/tinc@.service.in index 8fbf551..2d695ca 100644 --- a/systemd/tinc@.service.in +++ b/systemd/tinc@.service.in @@ -10,7 +10,7 @@ ReloadPropagatedFrom=tinc.service Type=simple WorkingDirectory=@sysconfdir@/tinc/%i ExecStart=@sbindir@/tincd -n %i -D -ExecReload=@sbindir@/tincd -n %i -kHUP +ExecReload=@sbindir@/tinc -n %i reload KillMode=mixed Restart=on-failure RestartSec=5 diff --git a/test-driver b/test-driver new file mode 100755 index 0000000..9759384 --- /dev/null +++ b/test-driver @@ -0,0 +1,150 @@ +#! /bin/sh +# test-driver - basic testsuite driver script. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 2011-2020 Free Software Foundation, Inc. +# +# 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, 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, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +usage_error () +{ + echo "$0: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <$log_file 2>&1 +estatus=$? + +if test $enable_hard_errors = no && test $estatus -eq 99; then + tweaked_estatus=1 +else + tweaked_estatus=$estatus +fi + +case $tweaked_estatus:$expect_failure in + 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; + 0:*) col=$grn res=PASS recheck=no gcopy=no;; + 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; + 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; + *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; + *:*) col=$red res=FAIL recheck=yes gcopy=yes;; +esac + +# Report the test outcome and exit status in the logs, so that one can +# know whether the test passed or failed simply by looking at the '.log' +# file, without the need of also peaking into the corresponding '.trs' +# file (automake bug#11814). +echo "$res $test_name (exit status: $estatus)" >>$log_file + +# Report outcome to console. +echo "${col}${res}${std}: $test_name" + +# Register the test result, and other relevant metadata. +echo ":test-result: $res" > $trs_file +echo ":global-test-result: $res" >> $trs_file +echo ":recheck: $recheck" >> $trs_file +echo ":copy-in-global-log: $gcopy" >> $trs_file + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..dae30dc --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,28 @@ +TESTS = \ + basic.test \ + commandline.test \ + executables.test \ + import-export.test \ + invite-join.test \ + invite-offline.test \ + invite-tinc-up.test \ + legacy-protocol.test \ + ns-ping.test \ + scripts.test \ + security.test \ + sptps-basic.test \ + variables.test + +dist_check_SCRIPTS = $(TESTS) + +AM_CFLAGS = -iquote. + +check_PROGRAMS = \ + splice + +splice_SOURCES = splice.c + +clean-local: + -for pid in *.test.?/pid; do ../src/tinc --pidfile="$$pid" stop; done + -killall ../src/sptps_test + -rm -rf *.test.? diff --git a/test/Makefile.in b/test/Makefile.in new file mode 100644 index 0000000..86fe520 --- /dev/null +++ b/test/Makefile.in @@ -0,0 +1,953 @@ +# Makefile.in generated by automake 1.16.3 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2020 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +check_PROGRAMS = splice$(EXEEXT) +subdir = test +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ + $(top_srcdir)/m4/ax_append_flag.m4 \ + $(top_srcdir)/m4/ax_cflags_warn_all.m4 \ + $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/ax_code_coverage.m4 \ + $(top_srcdir)/m4/ax_require_defined.m4 \ + $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libgcrypt.m4 \ + $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/miniupnpc.m4 \ + $(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dist_check_SCRIPTS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = testlib.sh +CONFIG_CLEAN_VPATH_FILES = +am_splice_OBJECTS = splice.$(OBJEXT) +splice_OBJECTS = $(am_splice_OBJECTS) +splice_LDADD = $(LDADD) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/splice.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(splice_SOURCES) +DIST_SOURCES = $(splice_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red=''; \ + grn=''; \ + lgn=''; \ + blu=''; \ + mgn=''; \ + brg=''; \ + std=''; \ + fi; \ +} +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__recheck_rx = ^[ ]*:recheck:[ ]* +am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* +am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* +# A command that, given a newline-separated list of test names on the +# standard input, print the name of the tests that are to be re-run +# upon "make recheck". +am__list_recheck_tests = $(AWK) '{ \ + recheck = 1; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + { \ + if ((getline line2 < ($$0 ".log")) < 0) \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ + { \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ + { \ + break; \ + } \ + }; \ + if (recheck) \ + print $$0; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# A command that, given a newline-separated list of test names on the +# standard input, create the global log from their .trs and .log files. +am__create_global_log = $(AWK) ' \ +function fatal(msg) \ +{ \ + print "fatal: making $@: " msg | "cat >&2"; \ + exit 1; \ +} \ +function rst_section(header) \ +{ \ + print header; \ + len = length(header); \ + for (i = 1; i <= len; i = i + 1) \ + printf "="; \ + printf "\n\n"; \ +} \ +{ \ + copy_in_global_log = 1; \ + global_test_result = "RUN"; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".trs"); \ + if (line ~ /$(am__global_test_result_rx)/) \ + { \ + sub("$(am__global_test_result_rx)", "", line); \ + sub("[ ]*$$", "", line); \ + global_test_result = line; \ + } \ + else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ + copy_in_global_log = 0; \ + }; \ + if (copy_in_global_log) \ + { \ + rst_section(global_test_result ": " $$0); \ + while ((rc = (getline line < ($$0 ".log"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".log"); \ + print line; \ + }; \ + printf "\n"; \ + }; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# Restructured Text title. +am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } +# Solaris 10 'make', and several other traditional 'make' implementations, +# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it +# by disabling -e (using the XSI extension "set +e") if it's set. +am__sh_e_setup = case $$- in *e*) set +e;; esac +# Default flags passed to test drivers. +am__common_driver_flags = \ + --color-tests "$$am__color_tests" \ + --enable-hard-errors "$$am__enable_hard_errors" \ + --expect-failure "$$am__expect_failure" +# To be inserted before the command running the test. Creates the +# directory for the log if needed. Stores in $dir the directory +# containing $f, in $tst the test, in $log the log. Executes the +# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and +# passes TESTS_ENVIRONMENT. Set up options for the wrapper that +# will run the test scripts (or their associated LOG_COMPILER, if +# thy have one). +am__check_pre = \ +$(am__sh_e_setup); \ +$(am__vpath_adj_setup) $(am__vpath_adj) \ +$(am__tty_colors); \ +srcdir=$(srcdir); export srcdir; \ +case "$@" in \ + */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ + *) am__odir=.;; \ +esac; \ +test "x$$am__odir" = x"." || test -d "$$am__odir" \ + || $(MKDIR_P) "$$am__odir" || exit $$?; \ +if test -f "./$$f"; then dir=./; \ +elif test -f "$$f"; then dir=; \ +else dir="$(srcdir)/"; fi; \ +tst=$$dir$$f; log='$@'; \ +if test -n '$(DISABLE_HARD_ERRORS)'; then \ + am__enable_hard_errors=no; \ +else \ + am__enable_hard_errors=yes; \ +fi; \ +case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ + am__expect_failure=yes;; \ + *) \ + am__expect_failure=no;; \ +esac; \ +$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) +# A shell command to get the names of the tests scripts with any registered +# extension removed (i.e., equivalently, the names of the test logs, with +# the '.log' extension removed). The result is saved in the shell variable +# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, +# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", +# since that might cause problem with VPATH rewrites for suffix-less tests. +# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. +am__set_TESTS_bases = \ + bases='$(TEST_LOGS)'; \ + bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ + bases=`echo $$bases` +AM_TESTSUITE_SUMMARY_HEADER = ' for $(PACKAGE_STRING)' +RECHECK_LOGS = $(TEST_LOGS) +AM_RECURSIVE_TARGETS = check recheck +TEST_SUITE_LOG = test-suite.log +TEST_EXTENSIONS = @EXEEXT@ .test +am__test_logs1 = $(TESTS:=.log) +am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) +TEST_LOGS = $(am__test_logs2:.test.log=.log) +TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver +TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ + $(TEST_LOG_FLAGS) +am__set_b = \ + case '$@' in \ + */*) \ + case '$*' in \ + */*) b='$*';; \ + *) b=`echo '$@' | sed 's/\.log$$//'`; \ + esac;; \ + *) \ + b='$*';; \ + esac +am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/testlib.sh.in \ + $(top_srcdir)/depcomp $(top_srcdir)/test-driver +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CODE_COVERAGE_CFLAGS = @CODE_COVERAGE_CFLAGS@ +CODE_COVERAGE_CPPFLAGS = @CODE_COVERAGE_CPPFLAGS@ +CODE_COVERAGE_CXXFLAGS = @CODE_COVERAGE_CXXFLAGS@ +CODE_COVERAGE_ENABLED = @CODE_COVERAGE_ENABLED@ +CODE_COVERAGE_LDFLAGS = @CODE_COVERAGE_LDFLAGS@ +CODE_COVERAGE_LIBS = @CODE_COVERAGE_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CURSES_LIBS = @CURSES_LIBS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +GCOV = @GCOV@ +GENHTML = @GENHTML@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LCOV = @LCOV@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MINIUPNPC_LIBS = @MINIUPNPC_LIBS@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +READLINE_LIBS = @READLINE_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +systemd_path = @systemd_path@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +TESTS = \ + basic.test \ + commandline.test \ + executables.test \ + import-export.test \ + invite-join.test \ + invite-offline.test \ + invite-tinc-up.test \ + legacy-protocol.test \ + ns-ping.test \ + scripts.test \ + security.test \ + sptps-basic.test \ + variables.test + +dist_check_SCRIPTS = $(TESTS) +AM_CFLAGS = -iquote. +splice_SOURCES = splice.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .log .o .obj .test .test$(EXEEXT) .trs +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu test/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu test/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +testlib.sh: $(top_builddir)/config.status $(srcdir)/testlib.sh.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ + +clean-checkPROGRAMS: + -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS) + +splice$(EXEEXT): $(splice_OBJECTS) $(splice_DEPENDENCIES) $(EXTRA_splice_DEPENDENCIES) + @rm -f splice$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(splice_OBJECTS) $(splice_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/splice.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +# Recover from deleted '.trs' file; this should ensure that +# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create +# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells +# to avoid problems with "make -n". +.log.trs: + rm -f $< $@ + $(MAKE) $(AM_MAKEFLAGS) $< + +# Leading 'am--fnord' is there to ensure the list of targets does not +# expand to empty, as could happen e.g. with make check TESTS=''. +am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) +am--force-recheck: + @: + +$(TEST_SUITE_LOG): $(TEST_LOGS) + @$(am__set_TESTS_bases); \ + am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ + redo_bases=`for i in $$bases; do \ + am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ + done`; \ + if test -n "$$redo_bases"; then \ + redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ + redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ + if $(am__make_dryrun); then :; else \ + rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ + fi; \ + fi; \ + if test -n "$$am__remaking_logs"; then \ + echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ + "recursion detected" >&2; \ + elif test -n "$$redo_logs"; then \ + am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ + fi; \ + if $(am__make_dryrun); then :; else \ + st=0; \ + errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ + for i in $$redo_bases; do \ + test -f $$i.trs && test -r $$i.trs \ + || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ + test -f $$i.log && test -r $$i.log \ + || { echo "$$errmsg $$i.log" >&2; st=1; }; \ + done; \ + test $$st -eq 0 || exit 1; \ + fi + @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ + ws='[ ]'; \ + results=`for b in $$bases; do echo $$b.trs; done`; \ + test -n "$$results" || results=/dev/null; \ + all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ + pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ + fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ + skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ + xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ + xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ + error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ + if test `expr $$fail + $$xpass + $$error` -eq 0; then \ + success=true; \ + else \ + success=false; \ + fi; \ + br='==================='; br=$$br$$br$$br$$br; \ + result_count () \ + { \ + if test x"$$1" = x"--maybe-color"; then \ + maybe_colorize=yes; \ + elif test x"$$1" = x"--no-color"; then \ + maybe_colorize=no; \ + else \ + echo "$@: invalid 'result_count' usage" >&2; exit 4; \ + fi; \ + shift; \ + desc=$$1 count=$$2; \ + if test $$maybe_colorize = yes && test $$count -gt 0; then \ + color_start=$$3 color_end=$$std; \ + else \ + color_start= color_end=; \ + fi; \ + echo "$${color_start}# $$desc $$count$${color_end}"; \ + }; \ + create_testsuite_report () \ + { \ + result_count $$1 "TOTAL:" $$all "$$brg"; \ + result_count $$1 "PASS: " $$pass "$$grn"; \ + result_count $$1 "SKIP: " $$skip "$$blu"; \ + result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ + result_count $$1 "FAIL: " $$fail "$$red"; \ + result_count $$1 "XPASS:" $$xpass "$$red"; \ + result_count $$1 "ERROR:" $$error "$$mgn"; \ + }; \ + { \ + echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ + $(am__rst_title); \ + create_testsuite_report --no-color; \ + echo; \ + echo ".. contents:: :depth: 2"; \ + echo; \ + for b in $$bases; do echo $$b; done \ + | $(am__create_global_log); \ + } >$(TEST_SUITE_LOG).tmp || exit 1; \ + mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ + if $$success; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ + fi; \ + echo "$${col}$$br$${std}"; \ + echo "$${col}Testsuite summary"$(AM_TESTSUITE_SUMMARY_HEADER)"$${std}"; \ + echo "$${col}$$br$${std}"; \ + create_testsuite_report --maybe-color; \ + echo "$$col$$br$$std"; \ + if $$success; then :; else \ + echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \ + if test -n "$(PACKAGE_BUGREPORT)"; then \ + echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \ + fi; \ + echo "$$col$$br$$std"; \ + fi; \ + $$success || exit 1 + +check-TESTS: $(check_PROGRAMS) $(dist_check_SCRIPTS) + @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list + @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + trs_list=`for i in $$bases; do echo $$i.trs; done`; \ + log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ + exit $$?; +recheck: all $(check_PROGRAMS) $(dist_check_SCRIPTS) + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + bases=`for i in $$bases; do echo $$i; done \ + | $(am__list_recheck_tests)` || exit 1; \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + log_list=`echo $$log_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ + am__force_recheck=am--force-recheck \ + TEST_LOGS="$$log_list"; \ + exit $$? +.test.log: + @p='$<'; \ + $(am__set_b); \ + $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +@am__EXEEXT_TRUE@.test$(EXEEXT).log: +@am__EXEEXT_TRUE@ @p='$<'; \ +@am__EXEEXT_TRUE@ $(am__set_b); \ +@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ +@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ +@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ +@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS) \ + $(dist_check_SCRIPTS) + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS) + -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs) + -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-checkPROGRAMS clean-generic clean-local mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/splice.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/splice.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-checkPROGRAMS clean-generic clean-local \ + cscopelist-am ctags ctags-am distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am recheck tags tags-am \ + uninstall uninstall-am + +.PRECIOUS: Makefile + + +clean-local: + -for pid in *.test.?/pid; do ../src/tinc --pidfile="$$pid" stop; done + -killall ../src/sptps_test + -rm -rf *.test.? + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/test/basic.test b/test/basic.test new file mode 100755 index 0000000..c377202 --- /dev/null +++ b/test/basic.test @@ -0,0 +1,20 @@ +#!/bin/sh + +. "${0%/*}/testlib.sh" + +# Initialize and test one node + +$tinc $c1 init foo +$tinc $c1 set DeviceType dummy +$tinc $c1 set Port 0 + +# Test running in the foreground + +(sleep 1; $tinc $c1 stop) & +$tinc $c1 start $r1 -D + +# Test running tinc in the background + +$tinc $c1 start $r1 +sleep 1 +$tinc $c1 stop diff --git a/test/commandline.test b/test/commandline.test new file mode 100755 index 0000000..f785b22 --- /dev/null +++ b/test/commandline.test @@ -0,0 +1,54 @@ +#!/bin/sh + +. "${0%/*}/testlib.sh" + +# Initialize one node + +$tinc $c1 <$d1/tinc-up <$d1/tinc-up.cmd <$d1/invitation-created <\$INVITATION_FILE +echo Ifconfig = 93.184.216.34/24 >>\$INVITATION_FILE +echo Route = 2606:2800:220:1::/64 2606:2800:220:1:248:1893:25c8:1946 >>\$INVITATION_FILE +echo Route = 1.2.3.4 1234:: >>\$INVITATION_FILE +$tinc $c1 export >>\$INVITATION_FILE +EOF + +cat >$d1/invitation-created.cmd <%INVITATION_FILE% +echo Ifconfig = 93.184.216.34/24 >>%INVITATION_FILE% +echo Route = 2606:2800:220:1::/64 2606:2800:220:1:248:1893:25c8:1946 >>%INVITATION_FILE% +echo Route = 1.2.3.4 1234:: >>%INVITATION_FILE% +$tinc $c1 export >>%INVITATION_FILE% +EOF + +chmod u+x $d1/invitation-created + +$tinc $c1 invite bar | tail -1 | $tinc $c2 --batch join + +# Test equivalence of host config files + +cmp $d1/hosts/foo $d2/hosts/foo +test "`grep ^Ed25519PublicKey $d1/hosts/bar`" = "`grep ^Ed25519PublicKey $d2/hosts/bar`" + +# Check if the tinc-up.invitation file is created and contains the right commands + +test -f $d2/tinc-up.invitation + +fgrep -q "93.184.216.34/24" $d2/tinc-up.invitation +fgrep -q "2606:2800:220:1::/64" $d2/tinc-up.invitation +fgrep -q "2606:2800:220:1:248:1893:25c8:1946" $d2/tinc-up.invitation +fgrep -q "1234::" $d2/tinc-up.invitation && exit 1 + +# Check that no tinc-up is created and that tinc-up.invitation is not executable + +test -x $d2/tinc-up.invitation && exit 1 +test -f $d2/tinc-up && exit 1 + +$tinc $c1 stop diff --git a/test/legacy-protocol.test b/test/legacy-protocol.test new file mode 100755 index 0000000..929d222 --- /dev/null +++ b/test/legacy-protocol.test @@ -0,0 +1,84 @@ +#!/bin/sh + +# Skip this test if the legacy protocol is disabled +if grep -q "define DISABLE_LEGACY 1" "${0%/*}/../config.h"; then + exit 77 +fi + +. "${0%/*}/testlib.sh" + +# Initialize two nodes + +$tinc $c1 <$d1/tinc-up <$d2/tinc-up <$d1/$script << EOF +#!/bin/sh +echo $script \$NETNAME,\$NAME,\$DEVICE,\$IFACE,\$NODE,\$REMOTEADDRESS,\$REMOTEPORT,\$SUBNET,\$WEIGHT,\$INVITATION_FILE,\$INVITATION_URL,\$DEBUG >>$OUT +EOF +chmod u+x $d1/$script + +cat >$d1/$script.cmd << EOF +echo $script %NETNAME%,%NAME%,%DEVICE%,%IFACE%,%NODE%,%REMOTEADDRESS%,%REMOTEPORT%,%SUBNET%,%WEIGHT%,%INVITATION_FILE%,%INVITATION_URL%,%DEBUG% >>$OUT +EOF +done + +# Start server node + +echo Starting server node... + +$tinc $c1 -n netname start $r1 + +echo foo-started >>$OUT + +# Invite client node + +echo Inviting client node... + +url=`$tinc $c1 -n netname2 invite bar | sed 's/\r//'` +file=`cd $d1/invitations; ls | grep -v ed25519_key.priv` +echo bar-invited >>$OUT + +echo Joining client node... + +$tinc $c2 -n netname3 join $url +echo bar-joined >>$OUT + +# Start and stop client node + +echo Starting client node... + +$tinc $c2 << EOF +set DeviceType dummy +set Port 32760 +add Subnet 10.0.0.2 +add Subnet fec0::/64#5 +start $r2 +EOF + +sleep 1 + +echo bar-started >>$OUT + +$tinc $c1 debug 4 +$tinc $c2 stop + +sleep 1 + +echo bar-stopped >>$OUT + +$tinc $c1 debug 5 +$tinc $c2 start $r2 + +sleep 1 + +echo bar-started >>$OUT + +# Stop server node + +$tinc $c1 stop +sleep 1 +$tinc $c2 stop + +# Check if the script output is what is expected + +cat >$OUT.expected << EOF +tinc-up netname,foo,dummy,,,,,,,,,5 +subnet-up netname,foo,dummy,,foo,,,10.0.0.1,,,,5 +subnet-up netname,foo,dummy,,foo,,,fec0::/64,,,,5 +foo-started +invitation-created netname2,foo,,,bar,,,,,$d1/invitations/$file,$url, +bar-invited +invitation-accepted netname,foo,dummy,,bar,127.0.0.1,,,,,,5 +bar-joined +host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +bar-started +host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4 +hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,4 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,4 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,4 +bar-stopped +host-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-up netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-up netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +bar-started +host-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +hosts/bar-down netname,foo,dummy,,bar,127.0.0.1,32760,,,,,5 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,10.0.0.2,,,,5 +subnet-down netname,foo,dummy,,bar,127.0.0.1,32760,fec0::/64,5,,,5 +subnet-down netname,foo,dummy,,foo,,,10.0.0.1,,,,5 +subnet-down netname,foo,dummy,,foo,,,fec0::/64,,,,5 +tinc-down netname,foo,dummy,,,,,,,,,5 +EOF + +sed -i 's/\r//' $OUT +cmp $OUT $OUT.expected diff --git a/test/security.test b/test/security.test new file mode 100755 index 0000000..91d29e2 --- /dev/null +++ b/test/security.test @@ -0,0 +1,98 @@ +#!/bin/sh + +. "${0%/*}/testlib.sh" + +# Skip this test if tools are missing + +which socket >/dev/null || exit 77 +which timeout >/dev/null || exit 77 + +# Initialize two nodes + +$tinc $c1 < + + 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 "../src/system.h" + +#ifdef HAVE_MINGW +static const char *winerror(int err) { + static char buf[1024], *ptr; + + ptr = buf + snprintf(buf, sizeof(buf), "(%d) ", err); + + if(!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ptr, sizeof(buf) - (ptr - buf), NULL)) { + strncpy(buf, "(unable to format errormessage)", sizeof(buf)); + }; + + if((ptr = strchr(buf, '\r'))) { + *ptr = '\0'; + } + + return buf; +} + +#define strerror(x) ((x)>0?strerror(x):winerror(GetLastError())) +#define sockerrno WSAGetLastError() +#define sockstrerror(x) winerror(x) +#else +#define sockerrno errno +#define sockstrerror(x) strerror(x) +#endif + +int main(int argc, char *argv[]) { + if(argc < 7) { + fprintf(stderr, "Usage: %s name1 host1 port1 name2 host2 port2 [protocol]\n", argv[0]); + return 1; + } + + const char *protocol; + + if(argc >= 8) { + protocol = argv[7]; + } else { + protocol = "17.7"; + } + +#ifdef HAVE_MINGW + static struct WSAData wsa_state; + + if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { + return 1; + } + +#endif + int sock[2]; + char buf[1024]; + + struct addrinfo *ai, hint; + memset(&hint, 0, sizeof(hint)); + + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_flags = 0; + + for (int i = 0; i < 2; i++) { + if(getaddrinfo(argv[2 + 3 * i], argv[3 + 3 * i], &hint, &ai) || !ai) { + fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno)); + return 1; + } + + sock[i] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + + if(sock[i] == -1) { + fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno)); + return 1; + } + + if(connect(sock[i], ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not connect to %s: %s\n", argv[i + 3 * i], sockstrerror(sockerrno)); + return 1; + } + + fprintf(stderr, "Connected to %s\n", argv[1 + 3 * i]); + + /* Pretend to be the other one */ + int len = snprintf(buf, sizeof buf, "0 %s %s\n", argv[4 - 3 * i], protocol); + if (send(sock[i], buf, len, 0) != len) { + fprintf(stderr, "Error sending data to %s: %s\n", argv[1 + 3 * i], sockstrerror(sockerrno)); + return 1; + } + + /* Ignore the response */ + do { + if (recv(sock[i], buf, 1, 0) != 1) { + fprintf(stderr, "Error reading data from %s: %s\n", argv[1 + 3 * i], sockstrerror(sockerrno)); + return 1; + } + } while(*buf != '\n'); + } + + fprintf(stderr, "Splicing...\n"); + + int nfds = (sock[0] > sock[1] ? sock[0] : sock[1]) + 1; + + while(true) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock[0], &fds); + FD_SET(sock[1], &fds); + + if(select(nfds, &fds, NULL, NULL, NULL) <= 0) { + return 1; + } + + for(int i = 0; i < 2; i++ ) { + if(FD_ISSET(sock[i], &fds)) { + ssize_t len = recv(sock[i], buf, sizeof buf, 0); + + if(len < 0) { + fprintf(stderr, "Error while reading from %s: %s\n", argv[1 + i * 3], sockstrerror(sockerrno)); + return 1; + } + + if(len == 0) { + fprintf(stderr, "Connection closed by %s\n", argv[1 + i * 3]); + return 0; + } + + if(send(sock[i ^ 1], buf, len, 0) != len) { + fprintf(stderr, "Error while writing to %s: %s\n", argv[4 - i * 3], sockstrerror(sockerrno)); + return 1; + } + } + } + } + + return 0; +} diff --git a/test/sptps-basic.test b/test/sptps-basic.test new file mode 100755 index 0000000..b6d081f --- /dev/null +++ b/test/sptps-basic.test @@ -0,0 +1,32 @@ +#!/bin/sh + +. "${0%/*}/testlib.sh" + +# Skip this test if we did not compile sptps_test + +test -e $sptps_test -a -e $sptps_keypair || exit 77 + +# Generate keys + +mkdir -p $d1 + +$sptps_keypair $d1/server.priv $d1/server.pub +$sptps_keypair $d1/client.priv $d1/client.pub + +# Test transfer of a simple file. + +(sleep 1; $sptps_test -4 -q $d1/client.priv $d1/server.pub localhost 32750 $d1/out1 +cmp $d1/out1 Makefile + +$sptps_test -4 -q $d1/server.priv $d1/client.pub 32750 $d1/out2 +cmp $d1/out2 Makefile + +# Datagram mode + +$sptps_test -4 -dq $d1/server.priv $d1/client.pub 32750 $d1/out3 +cmp $d1/out3 Makefile diff --git a/test/testlib.sh.in b/test/testlib.sh.in new file mode 100644 index 0000000..6a091cf --- /dev/null +++ b/test/testlib.sh.in @@ -0,0 +1,46 @@ +#!/bin/sh + +# Paths to executables + +tincd=../src/tincd@EXEEXT@ +tinc=../src/tinc@EXEEXT@ +sptps_test=../src/sptps_test@EXEEXT@ +sptps_keypair=../src/sptps_keypair@EXEEXT@ + +# Test directories + +scriptname=`basename $0` + +n1=$scriptname.1 +n2=$scriptname.2 +n3=$scriptname.3 + +d1=$PWD/$n1 +d2=$PWD/$n2 +d3=$PWD/$n3 + +# Default arguments for both tinc and tincd + +c1="-n $n1 --config=$d1 --pidfile=$d1/pid" +c2="-n $n2 --config=$d2 --pidfile=$d2/pid" +c3="-n $n3 --config=$d3 --pidfile=$d3/pid" + +# Arguments when running tincd + +r1="--logfile=$d1/log -d5" +r2="--logfile=$d2/log -d5" +r3="--logfile=$d3/log -d5" + +# Check for leftover tinc daemons + +[ -f $d1/pid ] && $tinc $c1 stop +[ -f $d2/pid ] && $tinc $c2 stop +[ -f $d3/pid ] && $tinc $c3 stop + +# Remove test directories + +rm -rf $d1 $d2 $d3 + +# Exit on errors, log all commands being executed + +set -ex diff --git a/test/variables.test b/test/variables.test new file mode 100755 index 0000000..6ae0b79 --- /dev/null +++ b/test/variables.test @@ -0,0 +1,86 @@ +#!/bin/sh + +. "${0%/*}/testlib.sh" + +# Initialize one node + +$tinc $c1 init foo +test "`$tinc $c1 get Name | sed 's/\r//'`" = "foo" + +# Test case sensitivity + +$tinc $c1 set Mode switch +test "`$tinc $c1 get Mode | sed 's/\r//'`" = "switch" +test "`$tinc $c1 get mode | sed 's/\r//'`" = "switch" +$tinc $c1 set mode router +test "`$tinc $c1 get Mode | sed 's/\r//'`" = "router" +test "`$tinc $c1 get mode | sed 's/\r//'`" = "router" +$tinc $c1 set Mode Switch +test "`$tinc $c1 get Mode | sed 's/\r//'`" = "Switch" + +# Test deletion + +$tinc $c1 del Mode hub && exit 1 || true +$tinc $c1 del Mode switch +test -z "`$tinc $c1 get Mode`" + +# There can only be one Mode variable + +$tinc $c1 add Mode switch +$tinc $c1 add Mode hub +test "`$tinc $c1 get Mode | sed 's/\r//'`" = "hub" + +# Test addition/deletion of multivalued variables + +$tinc $c1 add Subnet 1 +$tinc $c1 add Subnet 2 +$tinc $c1 add Subnet 2 +$tinc $c1 add Subnet 3 +test "`$tinc $c1 get Subnet | sed 's/\r//'`" = "1 +2 +3" +$tinc $c1 del Subnet 2 +test "`$tinc $c1 get Subnet | sed 's/\r//'`" = "1 +3" +$tinc $c1 del Subnet +test -z "`$tinc $c1 get Subnet`" + +# We should not be able to get/set server variables using node.variable syntax + +test -z "`$tinc $c1 get foo.Name`" +$tinc $c1 set foo.Name bar && exit 1 || true + +# Test getting/setting host variables for other nodes + +touch $d1/hosts/bar + +$tinc $c1 add bar.PMTU 1 +$tinc $c1 add bar.PMTU 2 +test "`$tinc $c1 get bar.PMTU | sed 's/\r//'`" = "2" + +$tinc $c1 add bar.Subnet 1 +$tinc $c1 add bar.Subnet 2 +$tinc $c1 add bar.Subnet 2 +$tinc $c1 add bar.Subnet 3 +test "`$tinc $c1 get bar.Subnet | sed 's/\r//'`" = "1 +2 +3" +$tinc $c1 del bar.Subnet 2 +test "`$tinc $c1 get bar.Subnet | sed 's/\r//'`" = "1 +3" +$tinc $c1 del bar.Subnet +test -z "`$tinc $c1 get bar.Subnet`" + +# We should not be able to get/set for nodes with invalid names + +touch $d1/hosts/qu-ux + +$tinc $c1 set qu-ux.Subnet 1 && exit 1 || true + +# We should not be able to set obsolete variables unless forced + +$tinc $c1 set PrivateKey 12345 && exit 1 || true +$tinc $c1 --force set PrivateKey 12345 +test "`$tinc $c1 get PrivateKey | sed 's/\r//'`" = "12345" +$tinc $c1 del PrivateKey +test -z "`$tinc $c1 get PrivateKey`"