+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.
-This is the README file for tinc version 1.1pre10. Installation
+This is the README file for tinc version 1.1pre11. Installation
instructions may be found in the INSTALL file.
tinc is Copyright (C) 1998-2014 by:
Compatibility
-------------
-Version 1.1pre10 is compatible with 1.0pre8, 1.0 and later, but not with older
+Version 1.1pre11 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 and 1.1pre10 itself, but not with any other 1.1preX version.
+1.0.X and 1.1pre11 itself, but not with any other 1.1preX version.
Requirements
We would like to thank the following people for their contributions to tinc:
* Alexander Reil and Gemeinde Berg
+* Alexander Ried
+* Alexis Hildebrandt
* Allesandro Gatti
* Andreas van Cranenburgh
* Anthony G. Basile
* Armijn Hemel
+* Armin Fisslthaler
+* Baptiste Jonglez
+* Borg
* Brandon Black
* Cheng LI
* Cris van Pelt
* Darius Jahandarie
+* David Pflug
* Delf Eldkraft
* Dennis Joachimsthaler
* dnk
* Etienne Dechamps
* Florent Clairambault
* Flynn Marquardt
+* Franz Pletz
* Gary Kessler and Claudia Gonzalez
* Grzegorz Dymarek
* Hans Bayle
* James MacLean
* Jamie Briggs
* Jason Harper
+* Jason Livesay
* Jelle de Jong
* Jeroen Ubbink
* Jerome Etienne
+* Jochen Voss
* Julien Muchembled
* Lavrans Laading
+* Loïc Dachary
* Loïc Grenié
* LubomÃr Bulej
* Mads Kiilerich
* Philipp Babel
* Robert van der Meulen
* Rumko
+* Saverio Proto
* Scott Lamb
+* Steffan Karger
* Sven-Haegar Koch
* Teemu Kiviniemi
+* Thomas Tsiakalakis
* Timothy Redaelli
+* Tomislav ÄŒohar
+* Tommy Arnkværn
* Tonnerre Lombard
* Vil Brekin
+* Vittorio Gambaletta
* Wessel Dankers
+* William A. Kennington III
+* William McArthur
* Wouter van Heyst
And everyone we forgot (if we did, please let us know). Thank you!
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 Cipher ClampMSS Compression ConnectTo DecrementTTL Device DeviceType Digest DirectOnly Ed25519PrivateKeyFile Ed25519PublicKey Ed25519PublicKeyFile ExperimentalProtocol Forwarding GraphDumpFile Hostnames IffOneQueue IndirectData Interface KeyExpire ListenAddress LocalDiscovery MACExpire MACLength MaxOutputBufferSize MaxTimeout Mode Name PMTU PMTUDiscovery PingInterval PingTimeout Port PriorityInheritance PrivateKeyFile ProcessPriority Proxy PublicKeyFile ReplayWindow StrictSubnets Subnet TCPOnly TunnelServer UDPRcvBuf UDPSndBuf VDEGroup VDEPort Weight"
+ confvars="Address AddressFamily BindToAddress BindToInterface Broadcast BroadcastSubnet Cipher ClampMSS Compression ConnectTo DecrementTTL Device DeviceStandby DeviceType Digest DirectOnly Ed25519PrivateKeyFile Ed25519PublicKey Ed25519PublicKeyFile ExperimentalProtocol Forwarding GraphDumpFile Hostnames IffOneQueue IndirectData Interface KeyExpire ListenAddress LocalDiscovery MACExpire MACLength MaxOutputBufferSize MaxTimeout Mode Name PMTU PMTUDiscovery PingInterval PingTimeout Port PriorityInheritance PrivateKeyFile ProcessPriority Proxy PublicKeyFile ReplayWindow StrictSubnets Subnet TCPOnly TunnelServer UDPDiscovery UDPDiscoveryKeepaliveInterval UDPDiscoveryInterval UDPDiscoveryTimeout UDPRcvBuf UDPSndBuf 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 log network pcap pid purge reload restart retry set start stop top version"
case ${prev} in
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
-AC_INIT([tinc], [1.1pre10])
+AC_INIT([tinc], [1.1pre11])
AC_CONFIG_SRCDIR([src/tincd.c])
AC_GNU_SOURCE
AM_INIT_AUTOMAKE([check-news std-options subdir-objects -Wall])
AC_ARG_ENABLE([hardening], AS_HELP_STRING([--disable-hardening], [disable compiler and linker hardening flags]))
AS_IF([test "x$enable_hardening" != "xno"],
- [AX_CHECK_COMPILE_FLAG([-DFORTIFY_SOURCE=2], [CPPFLAGS="$CPPFLAGS -DFORITFY_SOURCE=2"])
+ [AX_CHECK_COMPILE_FLAG([-DFORTIFY_SOURCE=2], [CPPFLAGS="$CPPFLAGS -DFORTIFY_SOURCE=2"])
AX_CHECK_COMPILE_FLAG([-fno-strict-overflow], [CPPFLAGS="$CPPFLAGS -fno-strict-overflow"])
AX_CHECK_COMPILE_FLAG([-fwrapv], [CPPFLAGS="$CPPFLAGS -fwrapv"])
case $host_os in
[], [], [#include "src/have.h"]
)
-AC_CHECK_DECLS([res_init], [LIBS="$LIBS -lresolv"], [], [
+AC_CHECK_DECLS([res_init], [AC_CHECK_LIB(resolv, res_init)], [], [
#include <netinet/in.h>
#include <resolv.h>
])
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
-if test -n "$with_libgcrypt"; then
- gcrypt=true
- tinc_LIBGCRYPT
-else
- openssl=true
- tinc_OPENSSL
+if test "x$enable_legacy_protocol" != "xno"; then
+ if test -n "$with_libgcrypt"; then
+ gcrypt=true
+ tinc_LIBGCRYPT
+ else
+ openssl=true
+ tinc_OPENSSL
+ fi
fi
-
+
AM_CONDITIONAL(OPENSSL, test -n "$openssl")
AM_CONDITIONAL(GCRYPT, test -n "$gcrypt")
.Op Fl -config Ns = Ns Ar DIR
.Op Fl -net Ns = Ns Ar NETNAME
.Op Fl -pidfile Ns = Ns Ar FILENAME
+.Op Fl -force
.Op Fl -help
.Op Fl -version
.Op Ar COMMAND
to authenticate with a running tinc daemon.
If unspecified, the default is
.Pa @localstatedir@/run/tinc. Ns Ar NETNAME Ns Pa .pid.
+.It Fl -force
+Force some commands to work despite warnings.
.It Fl -help
Display short list of options.
.It Fl -version
.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 .
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 Op Fl -force
+.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 Op Fl -force
+.It exchange
The same as export followed by import.
-.It exchange-all Op Fl -force
+.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 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.
.El
.Sh EXAMPLES
Examples of some commands:
IPv6 listening sockets will be created.
.It Va AutoConnect Li = yes | no Po no Pc Bq experimental
If set to yes,
-.Nm
+.Nm tinc
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 ,
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
.Li *
for the
.Ar address .
-.It Va LocalDiscovery Li = yes | no Pq no
+.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.
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.
-.It Va LocalDiscoveryAddress Li = Ar address
-If this variable is specified, local discovery packets are sent to the given
-.Ar address .
+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 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
.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 (a-z, A-Z, 0-9 and _), and is case sensitive.
+.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
.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/
directory.
Setting this options also implicitly sets StrictSubnets.
+.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 UDPRcvBuf Li = Ar bytes Pq OS default
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.
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.
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.
Note that you can only use one device per daemon.
See also @ref{Device files}.
+@cindex DeviceStandby
+@item DeviceStandby = <yes | no> (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.
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 LocalDiscoveryAddress
@item LocalDiscoveryAddress <@var{address}>
@file{@value{sysconfdir}/tinc/@var{netname}/hosts/} directory.
Setting this options also implicitly sets StrictSubnets.
+@cindex UDPDiscovey
+@item UDPDiscovery = <yes|no> (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 = <seconds> (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 = <seconds> (2)
+The minimum amount of time between sending UDP ping datagrams to try to establish UDP connectivity.
+
+@cindex UDPDiscoveryTimeout
+@item UDPDiscoveryTimeout = <seconds> (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 UDPRcvBuf
@item UDPRcvBuf = <bytes> (OS default)
Sets the socket receive buffer size for the UDP socket, in bytes.
If unspecified, the default is
@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
+@item --force
+Force some commands to work despite warnings.
+
@item --help
Display a short reminder of runtime options and commands, then terminate.
@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}]
Export all host configuration files to standard output.
@cindex import
-@item import [--force]
+@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 [--force]
+@item exchange
The same as export followed by import.
@cindex exchange-all
-@item exchange-all [--force]
+@item exchange-all
The same as export-all followed by import.
@cindex invite
from where it can be redirected to a file or piped through a program that can parse it directly,
such as tcpdump.
-@cindex network [@var{netname}]
-@item network
+@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.
+
@end table
@c ==================================================================
-#!/usr/bin/python
+#!/usr/bin/env python
# tinc-gui -- GUI for controlling a running tincd
# Copyright (C) 2009-2014 Guus Sliepen <guus@tinc-vpn.org>
self.to = args[1]
self.address = args[2]
self.port = args[4]
- self.options = int(args[5], 16)
- self.weight = int(args[6])
+ self.options = int(args[-2], 16)
+ self.weight = int(args[-1])
class Subnet:
def parse(self, args):
subnet.parse(resp[2:])
subnet.visited = True
self.subnets[(resp[2], resp[3])] = subnet
+ if subnet.owner == "(broadcast)":
+ continue
self.nodes[subnet.owner].subnets[resp[2]] = subnet
elif resp[1] == '6':
if len(resp) < 9:
self.list.InsertStringItem(i, subnet.address + '/' + subnet.prefixlen)
else:
self.list.SetStringItem(i, 0, subnet.address + '/' + subnet.prefixlen)
- self.list.SetStringItem(i, 1, subnet.weight)
+ self.list.SetStringItem(i, 1, str(subnet.weight))
self.list.SetStringItem(i, 2, subnet.owner)
self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner)
self.list.SetItemData(i, i)
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 openssl/ecdh.h openssl/ec.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([OpenSSL header files not found.]); break]
)
[AC_MSG_ERROR([OpenSSL libraries not found.])]
)
- AC_CHECK_FUNCS([RAND_pseudo_bytes EVP_EncryptInit_ex ECDH_compute_key ECDSA_verify], ,
+ AC_CHECK_FUNCS([RAND_status EVP_EncryptInit_ex], ,
[AC_MSG_ERROR([Missing OpenSSL functionality, make sure you have installed the latest version.]); break],
)
- AC_CHECK_DECLS([OpenSSL_add_all_algorithms, EVP_CTRL_GCM_GET_TAG], ,
+ AC_CHECK_DECLS([OpenSSL_add_all_algorithms], ,
[AC_MSG_ERROR([Missing OpenSSL functionality, make sure you have installed the latest version.]); break],
[#include <openssl/evp.h>]
)
sbin_PROGRAMS = tincd tinc sptps_test sptps_keypair
+## Make sure version.c is always rebuilt
+.PHONY: version.c
+version.c:
+
if LINUX
sbin_PROGRAMS += sptps_speed
endif
ed25519/keypair.c \
ed25519/precomp_data.h \
ed25519/sc.c ed25519/sc.h \
- ed25519/seed.c \
ed25519/sha512.c ed25519/sha512.h \
ed25519/sign.c \
ed25519/verify.c
tincd.c \
utils.c utils.h \
xalloc.h \
+ version.c version.h \
+ ed25519/ecdh.c \
+ ed25519/ecdsa.c \
$(ed25519_SOURCES) \
$(chacha_poly1305_SOURCES)
dropin.c dropin.h \
getopt.c getopt.h \
getopt1.c \
+ fsck.c fsck.h \
info.c info.h \
invitation.c invitation.h \
list.c list.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.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 = \
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)
openssl/cipher.c \
openssl/crypto.c \
openssl/digest.c openssl/digest.h \
- ed25519/ecdh.c \
- ed25519/ecdsa.c \
openssl/prf.c \
openssl/rsa.c
tinc_SOURCES += \
openssl/cipher.c \
openssl/crypto.c \
openssl/digest.c openssl/digest.h \
- ed25519/ecdh.c \
- ed25519/ecdsa.c \
- ed25519/ecdsagen.c \
openssl/prf.c \
openssl/rsa.c \
openssl/rsagen.c
sptps_test_SOURCES += \
openssl/crypto.c \
openssl/digest.c openssl/digest.h \
- ed25519/ecdh.c \
- ed25519/ecdsa.c \
openssl/prf.c
sptps_keypair_SOURCES += \
- openssl/crypto.c \
- ed25519/ecdsagen.c
+ openssl/crypto.c
sptps_speed_SOURCES += \
openssl/crypto.c \
openssl/digest.c openssl/digest.h \
- ed25519/ecdh.c \
- ed25519/ecdsa.c \
- ed25519/ecdsagen.c \
openssl/prf.c
-endif
-
+else
if GCRYPT
tincd_SOURCES += \
gcrypt/cipher.c \
gcrypt/crypto.c \
gcrypt/digest.c gcrypt/digest.h \
- gcrypt/ecdh.c \
- gcrypt/ecdsa.c \
gcrypt/prf.c \
gcrypt/rsa.c
tinc_SOURCES += \
gcrypt/cipher.c \
gcrypt/crypto.c \
gcrypt/digest.c gcrypt/digest.h \
- gcrypt/ecdh.c \
- gcrypt/ecdsa.c \
- gcrypt/ecdsagen.c \
gcrypt/prf.c \
gcrypt/rsa.c \
gcrypt/rsagen.c
gcrypt/cipher.c \
gcrypt/crypto.c \
gcrypt/digest.c gcrypt/digest.h \
- gcrypt/ecdh.c \
- gcrypt/ecdsa.c \
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
tinc_LDADD = $(READLINE_LIBS) $(CURSES_LIBS)
sptps_speed_LDADD = -lrt
-LIBS = @LIBS@
+LIBS = @LIBS@ -lm
if TUNEMU
LIBS += -lpcap
/*
device.c -- Interaction BSD tun/tap device
Copyright (C) 2001-2005 Ivo Timmermans,
- 2001-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2001-2014 Guus Sliepen <guus@tinc-vpn.org>
2009 Grzegorz Dymarek <gregd72002@googlemail.com>
This program is free software; you can redistribute it and/or modify
#endif
#define DEFAULT_TUN_DEVICE "/dev/tun0"
-#if defined(HAVE_FREEBSD) || defined(HAVE_NETBSD)
+#if defined(HAVE_DARWIN) || defined(HAVE_FREEBSD) || defined(HAVE_NETBSD)
#define DEFAULT_TAP_DEVICE "/dev/tap0"
#else
#define DEFAULT_TAP_DEVICE "/dev/tun0"
#endif
static bool setup_device(void) {
- char *type;
-
- 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);
- }
-
- if(!get_config_string(lookup_config(config_tree, "Interface"), &iface))
- iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device);
+ get_config_string(lookup_config(config_tree, "Device"), &device);
+ char *type;
if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) {
if(!strcasecmp(type, "tun"))
/* use default */;
return false;
}
} else {
- if(strstr(device, "tap") || routing_mode != RMODE_ROUTER)
+ if((device && strstr(device, "tap")) || routing_mode != RMODE_ROUTER)
device_type = DEVICE_TYPE_TAP;
}
+ if(!device) {
+ if(device_type == DEVICE_TYPE_TAP)
+ device = xstrdup(DEFAULT_TAP_DEVICE);
+ else
+ device = xstrdup(DEFAULT_TUN_DEVICE);
+ }
+
+ if(!get_config_string(lookup_config(config_tree, "Interface"), &iface))
+ iface = NULL;
+#ifndef TAPGIFNAME
+ if (iface) {
+ logger(DEBUG_ALWAYS, LOG_WARNING, "Ignoring specified interface name '%s' as device rename is not supported on this platform", iface);
+ free(iface);
+ iface = NULL;
+ }
+#endif
+ if (!iface)
+ iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device);
+
switch(device_type) {
#ifdef ENABLE_TUNEMU
case DEVICE_TYPE_TUNEMU: {
#ifdef ENABLE_TUNEMU
case DEVICE_TYPE_TUNEMU:
if(device_type == DEVICE_TYPE_TUNEMU)
- inlen = tunemu_read(device_fd, packet->data + 14, MTU - 14);
+ inlen = tunemu_read(device_fd, DATA(packet) + 14, MTU - 14);
else
#endif
- inlen = read(device_fd, packet->data + 14, MTU - 14);
+ inlen = read(device_fd, DATA(packet) + 14, MTU - 14);
if(inlen <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
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:
logger(DEBUG_TRAFFIC, LOG_ERR,
"Unknown IP version %d while reading packet from %s %s",
- packet->data[14] >> 4, device_info, device);
+ DATA(packet)[14] >> 4, device_info, device);
return false;
}
- memset(packet->data, 0, 12);
+ memset(DATA(packet), 0, 12);
packet->len = inlen + 14;
break;
case DEVICE_TYPE_TUNIFHEAD: {
u_int32_t type;
- struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, MTU - 14}};
+ struct iovec vector[2] = {{&type, sizeof type}, {DATA(packet) + 14, MTU - 14}};
if((inlen = readv(device_fd, vector, 2)) <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
switch (ntohl(type)) {
case AF_INET:
- packet->data[12] = 0x08;
- packet->data[13] = 0x00;
+ DATA(packet)[12] = 0x08;
+ DATA(packet)[13] = 0x00;
break;
case AF_INET6:
- packet->data[12] = 0x86;
- packet->data[13] = 0xDD;
+ DATA(packet)[12] = 0x86;
+ DATA(packet)[13] = 0xDD;
break;
default:
return false;
}
- memset(packet->data, 0, 12);
+ memset(DATA(packet), 0, 12);
packet->len = inlen + 10;
break;
}
case DEVICE_TYPE_TAP:
- if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
+ 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;
switch(device_type) {
case DEVICE_TYPE_TUN:
- if(write(device_fd, packet->data + 14, packet->len - 14) < 0) {
+ 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;
case DEVICE_TYPE_TUNIFHEAD: {
u_int32_t type;
- struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, packet->len - 14}};
+ struct iovec vector[2] = {{&type, sizeof type}, {DATA(packet) + 14, packet->len - 14}};
int af;
- af = (packet->data[12] << 8) + packet->data[13];
+ af = (DATA(packet)[12] << 8) + DATA(packet)[13];
switch (af) {
case 0x0800:
}
case DEVICE_TYPE_TAP:
- if(write(device_fd, packet->data, packet->len) < 0) {
+ 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;
#ifdef ENABLE_TUNEMU
case DEVICE_TYPE_TUNEMU:
- if(tunemu_write(device_fd, packet->data + 14, packet->len - 14) < 0) {
+ 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;
#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 *) __attribute__ ((__malloc__));
extern cipher_t *cipher_open_blowfish_ofb(void) __attribute__ ((__malloc__));
extern void cipher_close(cipher_t *);
extern size_t cipher_keylength(const cipher_t *);
+extern size_t cipher_blocksize(const cipher_t *);
extern void cipher_get_key(const cipher_t *, void *);
extern bool cipher_set_key(cipher_t *, void *, bool) __attribute__ ((__warn_unused_result__));
extern bool cipher_set_key_from_rsa(cipher_t *, void *, size_t, bool) __attribute__ ((__warn_unused_result__));
-extern bool cipher_set_counter(cipher_t *, const void *, size_t) __attribute__ ((__warn_unused_result__));
-extern bool cipher_set_counter_key(cipher_t *, void *) __attribute__ ((__warn_unused_result__));
extern bool cipher_encrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) __attribute__ ((__warn_unused_result__));
extern bool cipher_decrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_encrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_encrypt_start(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_encrypt_finish(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_decrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_decrypt_start(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
-extern bool cipher_gcm_decrypt_finish(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen) __attribute__ ((__warn_unused_result__));
extern int cipher_get_nid(const cipher_t *);
extern bool cipher_active(const cipher_t *);
#endif
+
+#endif
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);
- rsa_free(c->rsa);
free(c->hischallenge);
typedef struct connection_status_t {
unsigned int pinged:1; /* sent ping */
- unsigned int active:1; /* 1 if active.. */
+ 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 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 unused:19;
+ unsigned int unused:18;
} connection_status_t;
#include "ecdsa.h"
struct node_t *node; /* node associated with the other end */
struct edge_t *edge; /* edge associated with this connection */
+#ifndef DISABLE_LEGACY
rsa_t *rsa; /* his public RSA key */
- ecdsa_t *ecdsa; /* his public ECDSA 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;
+#endif
+
+ ecdsa_t *ecdsa; /* his public ECDSA key */
sptps_t sptps;
int inmaclength;
for list_each(connection_t, other, connection_list) {
if(strcmp(other->name, name))
continue;
- terminate_connection(other, other->status.active);
+ terminate_connection(other, other->edge);
found = true;
}
#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(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not create UNIX socket: %s", sockstrerror(sockerrno));
return false;
}
umask(mask);
if(result < 0) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind UNIX socket to %s: %s", unixsocketname, sockstrerror(errno));
+ 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(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not listen on UNIX socket %s: %s", unixsocketname, sockstrerror(sockerrno));
return false;
}
/*
device.c -- Interaction with Windows tap driver in a Cygwin environment
Copyright (C) 2002-2005 Ivo Timmermans,
- 2002-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2002-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
static bool read_packet(vpn_packet_t *packet) {
int inlen;
- if((inlen = read(sp[0], packet->data, MTU)) <= 0) {
+ if((inlen = read(sp[0], DATA(packet), MTU)) <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
return false;
logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
packet->len, device_info);
- if(!WriteFile (device_handle, packet->data, packet->len, &outlen, NULL)) {
+ if(!WriteFile (device_handle, DATA(packet), packet->len, &outlen, NULL)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
return false;
}
extern char *device;
extern char *iface;
-extern uint64_t device_in_packets;
-extern uint64_t device_in_bytes;
-extern uint64_t device_out_packets;
-extern uint64_t device_out_bytes;
-
typedef struct devops_t {
bool (*setup)(void);
void (*close)(void);
#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 bool digest_active(const digest_t *);
#endif
+
+#endif
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;
}
}
if(size) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Too little base64 data in PEM file\n");
+ if(data) {
+ errno = EINVAL;
+ logger(DEBUG_ALWAYS, LOG_ERR, "Too little base64 data in PEM file\n");
+ } else {
+ errno = ENOENT;
+ }
return false;
}
+++ /dev/null
-#include "ed25519.h"
-
-#ifndef ED25519_NO_SEED
-
-#ifdef _WIN32
-#include <windows.h>
-#include <wincrypt.h>
-#else
-#include <stdio.h>
-#endif
-
-int ed25519_create_seed(unsigned char *seed) {
-#ifdef _WIN32
- HCRYPTPROV prov;
-
- if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
- return 1;
- }
-
- if (!CryptGenRandom(prov, 32, seed)) {
- CryptReleaseContext(prov, 0);
- return 1;
- }
-
- CryptReleaseContext(prov, 0);
-#else
- FILE *f = fopen("/dev/urandom", "rb");
-
- if (f == NULL) {
- return 1;
- }
-
- fread(seed, 1, 32, f);
- fclose(f);
-#endif
-
- return 0;
-}
-
-#endif
\ No newline at end of file
/* the K array */
static const uint64_t K[80] = {
- UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd),
+ UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd),
UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
- UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
+ UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019),
UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
- UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe),
+ UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe),
UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
- UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1),
+ UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1),
UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
- UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
+ UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3),
UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
- UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483),
+ UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483),
UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
- UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210),
+ UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210),
UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
- UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
+ UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725),
UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
- UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926),
+ UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926),
UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
- UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8),
+ 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(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910),
UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
- UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53),
+ UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53),
UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
- UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
+ UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb),
UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
- UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60),
+ UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60),
UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
- UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9),
+ UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9),
UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
- UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
+ UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207),
UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
- UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6),
+ UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6),
UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
- UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493),
+ UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493),
UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
- UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
+ UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a),
UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817)
};
#define Ch(x,y,z) (z ^ (x & (y ^ z)))
-#define Maj(x,y,z) (((x | y) & z) | (x & y))
+#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))
#endif
/* compress 1024-bits */
-static int sha512_compress(sha512_context *md, unsigned char *buf)
+static int sha512_compress(sha512_context *md, const unsigned char *buf)
{
uint64_t S[8], W[80], t0, t1;
int 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) \
@param inlen The length of the data (octets)
@return 0 if successful
*/
-int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen)
-{
+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, (unsigned char *)in)) != 0) {
- return err;
- }
- md->length += 128 * 8;
- in += 128;
- inlen -= 128;
- } else {
+ 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->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;
+ 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;
}
/**
@param out [out] The destination of the hash (64 bytes)
@return 0 if successful
*/
- int sha512_final(sha512_context * md, unsigned char *out)
- {
+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;
- }
+ return 1;
+ }
/* increase the length of the message */
- md->length += md->curlen * UINT64_C(8);
+ md->length += md->curlen * UINT64_C(8);
/* append the '1' bit */
- md->buf[md->curlen++] = (unsigned char)0x80;
+ 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
md->curlen = 0;
}
- /* pad upto 120 bytes of zeroes
+ /* pad upto 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;
-}
+ while (md->curlen < 120) {
+ md->buf[md->curlen++] = (unsigned char)0;
+ }
/* store length */
-STORE64H(md->length, md->buf+120);
-sha512_compress(md, md->buf);
+ 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));
-}
+ for (i = 0; i < 8; i++) {
+ STORE64H(md->state[i], out+(8*i));
+ }
-return 0;
+ return 0;
}
-int sha512(const unsigned char *message, size_t message_len, unsigned char *out)
+int sha512(const void *message, size_t message_len, void *out)
{
sha512_context ctx;
int ret;
int sha512_init(sha512_context * md);
-int sha512_final(sha512_context * md, unsigned char *out);
-int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen);
-int sha512(const unsigned char *message, size_t message_len, unsigned char *out);
+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
\ No newline at end of file
+#endif
for splay_each(node_t, n, node_tree) {
for splay_each(edge_t, e, n->edge_tree) {
char *address = sockaddr2hostname(&e->address);
- send_request(c, "%d %d %s %s %s %x %d",
+ 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,
- e->options, e->weight);
+ local_address, e->options, e->weight);
free(address);
+ free(local_address);
}
}
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 */
#include "event.h"
#include "net.h"
#include "utils.h"
+#include "xalloc.h"
struct timeval now;
+#ifndef HAVE_MINGW
static fd_set readfds;
static fd_set writefds;
-static volatile bool running;
+#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 io_compare(const io_t *a, const io_t *b) {
+#ifndef HAVE_MINGW
return a->fd - b->fd;
+#else
+ return a->event - b->event;
+#endif
}
static int timeout_compare(const timeout_t *a, const timeout_t *b) {
return;
io->fd = fd;
+#ifdef HAVE_MINGW
+ if (io->fd != -1) {
+ io->event = WSACreateEvent();
+ if (io->event == WSA_INVALID_EVENT)
+ abort();
+ }
+ event_count++;
+#endif
io->cb = cb;
io->data = data;
io->node.data = io;
abort();
}
+#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;
io->flags = flags;
+ if (io->fd == -1)
+ return;
+#ifndef HAVE_MINGW
if(flags & IO_READ)
FD_SET(io->fd, &readfds);
else
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) {
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;
}
#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) {
- gettimeofday(&now, NULL);
- struct timeval diff, *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;
- }
- }
-
+ struct timeval diff;
+ struct timeval *tv = get_time_remaining(&diff);
memcpy(&readable, &readfds, sizeof readable);
memcpy(&writable, &writefds, sizeof writable);
fds = last->fd + 1;
}
-#ifdef HAVE_MINGW
- LeaveCriticalSection(&mutex);
-#endif
int n = select(fds, &readable, &writable, NULL, tv);
-#ifdef HAVE_MINGW
- EnterCriticalSection(&mutex);
-#endif
if(n < 0) {
- if(sockwouldblock(errno))
+ if(sockwouldblock(sockerrno))
continue;
else
return false;
io->cb(io->data, IO_READ);
}
}
+#else
+ while (running) {
+ struct timeval diff;
+ struct timeval *tv = get_time_remaining(&diff);
+ DWORD timeout_ms = tv ? (tv->tv_sec * 1000 + tv->tv_usec / 1000 + 1) : WSA_INFINITE;
+
+ if (!event_count) {
+ Sleep(timeout_ms);
+ continue;
+ }
- return true;
-}
+ /*
+ 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.
+ */
+ io_t* writeable_io = NULL;
+ for splay_each(io_t, io, &io_tree)
+ if (io->flags & IO_WRITE && send(io->fd, NULL, 0, 0) == 0) {
+ writeable_io = io;
+ break;
+ }
+ if (writeable_io) {
+ writeable_io->cb(writeable_io->data, IO_WRITE);
+ continue;
+ }
-void event_flush_output(void) {
- for splay_each(io_t, io, &io_tree)
- if(FD_ISSET(io->fd, &writefds))
- io->cb(io->data, IO_WRITE);
+ WSAEVENT* events = xmalloc(event_count * sizeof(*events));
+ DWORD event_index = 0;
+ for splay_each(io_t, io, &io_tree) {
+ events[event_index] = io->event;
+ event_index++;
+ }
+
+ DWORD result = WSAWaitForMultipleEvents(event_count, events, FALSE, timeout_ms, FALSE);
+
+ WSAEVENT event;
+ if (result >= WSA_WAIT_EVENT_0 && result < WSA_WAIT_EVENT_0 + event_count)
+ event = events[result - WSA_WAIT_EVENT_0];
+ free(events);
+ if (result == WSA_WAIT_TIMEOUT)
+ continue;
+ if (result < WSA_WAIT_EVENT_0 || result >= WSA_WAIT_EVENT_0 + event_count)
+ return false;
+
+ io_t *io = splay_search(&io_tree, &((io_t){.event = event}));
+ if (!io)
+ abort();
+
+ if (io->fd == -1) {
+ io->cb(io->data, 0);
+ } else {
+ WSANETWORKEVENTS network_events;
+ if (WSAEnumNetworkEvents(io->fd, io->event, &network_events) != 0)
+ return false;
+ if (network_events.lNetworkEvents & WRITE_EVENTS)
+ io->cb(io->data, IO_WRITE);
+ if (network_events.lNetworkEvents & READ_EVENTS)
+ io->cb(io->data, IO_READ);
+ }
+ }
+#endif
+
+ return true;
}
void event_exit(void) {
typedef struct io_t {
int fd;
int flags;
+#ifdef HAVE_MINGW
+ WSAEVENT event;
+#endif
io_cb_t cb;
void *data;
splay_node_t node;
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 signal_del(signal_t *sig);
extern bool event_loop(void);
-extern void event_flush_output(void);
extern void event_exit(void);
#endif
--- /dev/null
+/*
+ fsck.c -- Check the configuration files for problems
+ Copyright (C) 2014 Guus Sliepen <guus@tinc-vpn.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "system.h"
+
+#include "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) {
+ 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) {
+ uid_t uid = getuid();
+
+ // 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;
+ }
+
+ 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
+
+ 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;
+ }
+
+ 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);
+ }
+ }
+ }
+
+#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 and Ed25519PublicKeyFile variables 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[len], buf2[len], buf3[len];
+ randomize(buf1, sizeof buf1);
+ buf1[0] &= 0x7f;
+ memset(buf2, 0, sizeof buf2);
+ memset(buf3, 0, sizeof buf2);
+ if(!rsa_public_encrypt(rsa_pub, buf1, sizeof buf1, buf2)) {
+ fprintf(stderr, "ERROR: public RSA key does not work.\n");
+ return 1;
+ }
+ if(!rsa_private_decrypt(rsa_priv, buf2, sizeof buf2, buf3)) {
+ fprintf(stderr, "ERROR: private RSA key does not work.\n");
+ return 1;
+ }
+ if(memcmp(buf1, buf3, sizeof buf1)) {
+ fprintf(stderr, "ERROR: public and private RSA keys do not match.\n");
+ return 1;
+ }
+ }
+ } else {
+ if(rsa_pub)
+ fprintf(stderr, "WARNING: A public RSA key was found but no private key is known.\n");
+ }
+#endif
+ //
+ // TODO: this should read the Ed25519PublicKey config variable instead.
+ ecdsa_t *ecdsa_pub = NULL;
+
+ f = fopen(fname, "r");
+ if(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;
+}
+
--- /dev/null
+/*
+ fsck.h -- header for fsck.c.
+ Copyright (C) 2012 Guus Sliepen <guus@tinc-vpn.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#ifndef __TINC_FSCK_H__
+#define __TINC_FSCK_H__
+
+extern int fsck(const char *argv0);
+
+#endif
+
n->status.udp_confirmed = false;
n->maxmtu = MTU;
+ n->maxrecentlen = 0;
n->minmtu = 0;
n->mtuprobes = 0;
- timeout_del(&n->mtutimeout);
+ timeout_del(&n->udp_ping_timeout);
char *name;
char *address;
memset(&n->status, 0, sizeof n->status);
n->options = 0;
} else if(n->connection) {
- if(n->status.sptps) {
- if(n->connection->outgoing)
- send_req_key(n);
- } else {
+ // Speed up UDP probing by sending our key.
+ if(!n->status.sptps)
send_ans_key(n);
- }
}
}
if (device_standby) {
if (reachable_count == 0 && became_unreachable_count > 0)
device_disable();
- else if (reachable_count == became_reachable_count)
+ else if (reachable_count > 0 && reachable_count == became_reachable_count)
device_enable();
}
}
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) {
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);
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
+#include <math.h>
#ifdef HAVE_MINGW
#include <w32api.h>
#include <netinet/if_ether.h>
#endif
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
+#ifdef HAVE_RESOLV_H
+#include <resolv.h>
+#endif
+
+#ifdef STATUS
+#undef STATUS
+#endif
+
#ifdef HAVE_MINGW
#define SLASH "\\"
#else
#include "subnet.h"
#include "tincctl.h"
#include "info.h"
+#include "utils.h"
#include "xalloc.h"
void logger(int level, int priority, const char *format, ...) {
char line[4096];
char node[4096];
+ char id[4096];
char from[4096];
char to[4096];
char subnet[4096];
long int last_state_change;
while(recvline(fd, line, sizeof line)) {
- int n = sscanf(line, "%d %d %s %s port %s %d %d %d %d %x %"PRIx32" %s %s %d %hd %hd %hd %ld", &code, &req, node, host, port, &cipher, &digest, &maclength, &compression, &options, &status_union.raw, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
+ int n = sscanf(line, "%d %d %s %s %s port %s %d %d %d %d %x %"PRIx32" %s %s %d %hd %hd %hd %ld", &code, &req, node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_union.raw, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
if(n == 2)
break;
- if(n != 18) {
+ if(n != 19) {
fprintf(stderr, "Unable to parse node dump from tincd.\n");
return 1;
}
}
printf("Node: %s\n", item);
+ printf("Node ID: %s\n", id);
printf("Address: %s port %s\n", host, port);
char timestr[32] = "never";
#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) {
}
}
- char hash[25];
-
xasprintf(&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;
// Create a hash of the key.
+ char hash[64];
char *fingerprint = ecdsa_get_base64_public_key(key);
- digest_t *digest = digest_open_by_name("sha256", 18);
- if(!digest)
- abort();
- digest_create(digest, fingerprint, strlen(fingerprint), hash);
+ sha512(fingerprint, strlen(fingerprint), hash);
b64encode_urlsafe(hash, hash, 18);
// Create a random cookie for this invitation.
// Create a filename that doesn't reveal the cookie itself
char buf[18 + strlen(fingerprint)];
- char cookiehash[25];
+ char cookiehash[64];
memcpy(buf, cookie, 18);
memcpy(buf + 18, fingerprint, sizeof buf - 18);
- digest_create(digest, buf, sizeof buf, cookiehash);
+ sha512(buf, sizeof buf, cookiehash);
b64encode_urlsafe(cookiehash, cookiehash, 18);
b64encode_urlsafe(cookie, cookie, 18);
FILE *fh = fopen(filename, "w");
if(!fh) {
fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
+ fclose(f);
return false;
}
sptps_send_record(&sptps, 1, b64key, strlen(b64key));
free(b64key);
+ ecdsa_free(key);
-
+#ifndef DISABLE_LEGACY
rsa_t *rsa = rsa_generate(2048, 0x1001);
xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase);
f = fopenmask(filename, "w", 0600);
rsa_write_pem_public_key(rsa, fh);
fclose(fh);
- ecdsa_free(key);
rsa_free(rsa);
+#endif
check_port(name);
}
-static bool invitation_send(void *handle, uint8_t type, const char *data, size_t len) {
+static bool invitation_send(void *handle, uint8_t type, const void *data, size_t len) {
while(len) {
int result = send(sock, data, len, 0);
if(result == -1 && errno == EINTR)
return true;
}
-static bool invitation_receive(void *handle, uint8_t type, const char *msg, uint16_t len) {
+static bool invitation_receive(void *handle, uint8_t type, const void *msg, uint16_t len) {
switch(type) {
case SPTPS_HANDSHAKE:
return sptps_send_record(&sptps, 0, cookie, sizeof cookie);
// Check if the hash of the key he gave us matches the hash in the URL.
char *fingerprint = line + 2;
- digest_t *digest = digest_open_by_name("sha256", 18);
- if(!digest)
- abort();
- char hishash[18];
- if(!digest_create(digest, fingerprint, strlen(fingerprint), hishash)) {
+ char hishash[64];
+ if(sha512(fingerprint, strlen(fingerprint), hishash)) {
fprintf(stderr, "Could not create digest\n%s\n", line + 2);
return 1;
}
/*
device.c -- Interaction with Linux ethertap and tun/tap device
Copyright (C) 2001-2005 Ivo Timmermans,
- 2001-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2001-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
+ if(ifr.ifr_flags & IFF_TAP) {
+ struct ifreq ifr_mac = {};
+ 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));
+ }
+
return true;
}
switch(device_type) {
case DEVICE_TYPE_TUN:
- inlen = read(device_fd, packet->data + 10, MTU - 10);
+ inlen = read(device_fd, DATA(packet) + 10, MTU - 10);
if(inlen <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s",
return false;
}
- memset(packet->data, 0, 12);
+ memset(DATA(packet), 0, 12);
packet->len = inlen + 10;
break;
case DEVICE_TYPE_TAP:
- inlen = read(device_fd, packet->data, MTU);
+ inlen = read(device_fd, DATA(packet), MTU);
if(inlen <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s",
switch(device_type) {
case DEVICE_TYPE_TUN:
- packet->data[10] = packet->data[11] = 0;
- if(write(device_fd, packet->data + 10, packet->len - 10) < 0) {
+ DATA(packet)[10] = DATA(packet)[11] = 0;
+ 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;
}
break;
case DEVICE_TYPE_TAP:
- if(write(device_fd, packet->data, packet->len) < 0) {
+ 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;
#endif
#endif
+#include <stdbool.h>
+
extern debug_t debug_level;
extern bool logcontrol;
extern void openlogger(const char *, logmode_t);
/*
meta.c -- handle the meta communication
- Copyright (C) 2000-2013 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2000-2014 Guus Sliepen <guus@tinc-vpn.org>,
2000-2005 Ivo Timmermans
2006 Scott Lamb <slamb@slamb.org>
#include "utils.h"
#include "xalloc.h"
-bool send_meta_sptps(void *handle, uint8_t type, const char *buffer, size_t length) {
+bool send_meta_sptps(void *handle, uint8_t type, const void *buffer, size_t length) {
connection_t *c = handle;
if(!c) {
/* Add our data to buffer */
if(c->status.encryptout) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
size_t outlen = length;
if(!cipher_encrypt(c->outcipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) {
c->name, c->hostname);
return false;
}
+#endif
} else {
buffer_add(&c->outbuf, buffer, length);
}
void broadcast_meta(connection_t *from, const char *buffer, int length) {
for list_each(connection_t, c, connection_list)
- if(c != from && c->status.active)
+ if(c != from && c->edge)
send_meta(c, buffer, length);
}
-bool receive_meta_sptps(void *handle, uint8_t type, const char *data, uint16_t 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) {
inlen = recv(c->socket, inbuf, sizeof inbuf - c->inbuf.len, 0);
if(inlen <= 0) {
- if(!inlen || !errno) {
+ if(!inlen || !sockerrno) {
logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)",
c->name, c->hostname);
} else if(sockwouldblock(sockerrno))
inlen -= endp - bufp;
bufp = endp;
} else {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
size_t outlen = inlen;
if(!cipher_decrypt(c->incipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || inlen != outlen) {
}
inlen = 0;
+#endif
}
while(c->inbuf.len) {
/*
meta.h -- header for meta.c
- Copyright (C) 2000-2012 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2000-2014 Guus Sliepen <guus@tinc-vpn.org>,
2000-2005 Ivo Timmermans
This program is free software; you can redistribute it and/or modify
#include "connection.h"
extern bool send_meta(struct connection_t *, const char *, int);
-extern bool send_meta_sptps(void *, uint8_t, const char *, size_t);
-extern bool receive_meta_sptps(void *, uint8_t, const char *, uint16_t);
+extern bool send_meta_sptps(void *, uint8_t, const void *, size_t);
+extern bool receive_meta_sptps(void *, uint8_t, const void *, uint16_t);
extern void broadcast_meta(struct connection_t *, const char *, int);
extern bool receive_meta(struct connection_t *);
/*
device.c -- Interaction with Windows tap driver in a MinGW environment
Copyright (C) 2002-2005 Ivo Timmermans,
- 2002-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2002-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
int device_fd = -1;
static HANDLE device_handle = INVALID_HANDLE_VALUE;
+static io_t device_read_io;
+static OVERLAPPED device_read_overlapped;
+static vpn_packet_t device_read_packet;
char *device = NULL;
char *iface = NULL;
static char *device_info = NULL;
-static uint64_t device_total_in = 0;
-static uint64_t device_total_out = 0;
-
extern char *myport;
-static DWORD WINAPI tapreader(void *bla) {
- int status;
- DWORD len;
- OVERLAPPED overlapped;
- vpn_packet_t packet;
-
- logger(DEBUG_ALWAYS, LOG_DEBUG, "Tap reader running");
-
- /* Read from tap device and send to parent */
-
- overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-
- for(;;) {
- overlapped.Offset = 0;
- overlapped.OffsetHigh = 0;
- ResetEvent(overlapped.hEvent);
+static void device_issue_read() {
+ device_read_overlapped.Offset = 0;
+ device_read_overlapped.OffsetHigh = 0;
- status = ReadFile(device_handle, (void *)packet.data, MTU, &len, &overlapped);
-
- if(!status) {
- if(GetLastError() == ERROR_IO_PENDING) {
- WaitForSingleObject(overlapped.hEvent, INFINITE);
- if(!GetOverlappedResult(device_handle, &overlapped, &len, FALSE))
- continue;
- } else {
+ int status;
+ for (;;) {
+ DWORD len;
+ status = ReadFile(device_handle, (void *)device_read_packet.data, MTU, &len, &device_read_overlapped);
+ if (!status) {
+ if (GetLastError() != ERROR_IO_PENDING)
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
- return -1;
- }
+ break;
}
- EnterCriticalSection(&mutex);
- packet.len = len;
- packet.priority = 0;
- route(myself, &packet);
- event_flush_output();
- LeaveCriticalSection(&mutex);
+ device_read_packet.len = len;
+ device_read_packet.priority = 0;
+ route(myself, &device_read_packet);
+ }
+}
+
+static void device_handle_read(void *data, int flags) {
+ ResetEvent(device_read_overlapped.hEvent);
+
+ 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));
+ return;
}
+
+ device_read_packet.len = len;
+ device_read_packet.priority = 0;
+ route(myself, &device_read_packet);
+ device_issue_read();
}
static bool setup_device(void) {
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);
overwrite_mac = 1;
}
- /* Start the tap reader */
-
- thread = CreateThread(NULL, 0, tapreader, NULL, 0, NULL);
-
- if(!thread) {
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "CreateThread", winerror(GetLastError()));
- return false;
- }
-
device_info = "Windows tap device";
logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info);
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);
+
+ io_add_event(&device_read_io, device_handle_read, NULL, CreateEvent(NULL, TRUE, FALSE, NULL));
+ device_read_overlapped.hEvent = device_read_io.event;
+ device_issue_read();
}
static void disable_device(void) {
logger(DEBUG_ALWAYS, LOG_INFO, "Disabling %s", device_info);
+
+ io_del(&device_read_io);
+ CancelIo(device_handle);
+ CloseHandle(device_read_overlapped.hEvent);
+
ULONG status = 0;
DWORD len;
DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof status, &status, sizeof status, &len, NULL);
logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
packet->len, device_info);
- if(!WriteFile(device_handle, packet->data, packet->len, &outlen, &overlapped)) {
+ if(!WriteFile(device_handle, DATA(packet), packet->len, &outlen, &overlapped)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError()));
return false;
}
- device_total_out += packet->len;
-
return true;
}
#endif
default:
- logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family);
+ logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %x unsupported", ai->ai_family);
goto error;
}
static bool read_packet(vpn_packet_t *packet) {
int lenin;
- if((lenin = recv(device_fd, (void *)packet->data, MTU, 0)) <= 0) {
+ if((lenin = recv(device_fd, DATA(packet), MTU, 0)) <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
- device, strerror(errno));
+ device, sockstrerror(sockerrno));
return false;
}
- if(!memcmp(&ignore_src, packet->data + 6, sizeof ignore_src)) {
+ 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;
}
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) {
+ if(sendto(device_fd, 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,
- strerror(errno));
+ sockstrerror(sockerrno));
return false;
}
- memcpy(&ignore_src, packet->data + 6, sizeof ignore_src);
+ memcpy(&ignore_src, DATA(packet) + 6, sizeof ignore_src);
return true;
}
else
xasprintf(&confbase, "%s", installdir);
}
- if(!pidfilename)
- xasprintf(&pidfilename, "%s" SLASH "pid", confbase);
}
RegCloseKey(key);
}
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
+ if(!logfilename)
+ xasprintf(&logfilename, "%s" SLASH "log", confbase);
+
+ if(!pidfilename)
+ xasprintf(&pidfilename, "%s" SLASH "pid", confbase);
+#else
if(!logfilename)
xasprintf(&logfilename, LOCALSTATEDIR SLASH "log" SLASH "%s.log", identname);
if(!pidfilename)
xasprintf(&pidfilename, LOCALSTATEDIR SLASH "run" SLASH "%s.pid", identname);
+#endif
if(!unixsocketname) {
int len = strlen(pidfilename);
else
strcpy(unixsocketname + len, ".socket");
}
-
- if(!confbase) {
- if(netname)
- xasprintf(&confbase, CONFDIR SLASH "tinc" SLASH "%s", netname);
- else
- xasprintf(&confbase, CONFDIR SLASH "tinc");
- }
}
void free_names(void) {
#include "subnet.h"
#include "xalloc.h"
-#ifdef HAVE_RESOLV_H
-#include <resolv.h>
-#endif
-
int contradicting_add_edge = 0;
int contradicting_del_edge = 0;
static int sleeptime = 10;
void terminate_connection(connection_t *c, bool report) {
logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Closing connection with %s (%s)", c->name, c->hostname);
- c->status.active = false;
-
if(c->node && c->node->connection == c)
c->node->connection = NULL;
continue;
if(c->last_ping_time + pingtimeout <= now.tv_sec) {
- if(c->status.active) {
+ if(c->edge) {
+ try_tx(c->node, false);
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);
} else if(c->last_ping_time + pinginterval <= now.tv_sec) {
else
logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
}
- terminate_connection(c, c->status.active);
+ terminate_connection(c, c->edge);
}
+
}
- timeout_set(data, &(struct timeval){pingtimeout, rand() % 100000});
+ timeout_set(data, &(struct timeval){1, rand() % 100000});
}
static void periodic_handler(void *data) {
/* Count number of active connections */
int nc = 0;
for list_each(connection_t, c, connection_list) {
- if(c->status.active && !c->status.control)
+ if(c->edge)
nc++;
}
int i = 0;
for list_each(connection_t, c, connection_list) {
- if(!c->status.active || c->status.control)
+ if(!c->edge)
continue;
if(i++ != r)
logger(DEBUG_CONNECTIONS, LOG_INFO, "Autodisconnecting from %s", c->name);
list_delete(outgoing_list, c->outgoing);
c->outgoing = NULL;
- terminate_connection(c, c->status.active);
+ terminate_connection(c, c->edge);
break;
}
}
void handle_meta_connection_data(connection_t *c) {
if (!receive_meta(c)) {
- terminate_connection(c, c->status.active);
+ terminate_connection(c, c->edge);
return;
}
}
static void sigalrm_handler(void *data) {
logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(((signal_t *)data)->signum));
-#ifdef HAVE_DECL_RES_INIT
- res_init();
-#endif
retry();
}
#endif
if(strictsubnets) {
for splay_each(subnet_t, subnet, subnet_tree)
- subnet->expires = 1;
+ if (subnet->owner)
+ subnet->expires = 1;
load_all_subnets();
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)
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->status.active);
+ terminate_connection(c, c->edge);
}
free(fname);
}
#endif
if(!event_loop()) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", strerror(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
return 1;
}
#define MTU 1518 /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */
#endif
-/* MAXSIZE is the maximum size of an encapsulated packet: MTU + seqno + padding + HMAC + compressor overhead */
-#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20)
+/* 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)
/* 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)
uint16_t x[8];
} ipv6_t;
+typedef struct node_id_t {
+ uint8_t x[6];
+} node_id_t;
+
typedef short length_t;
+typedef uint32_t seqno_t;
#define AF_UNKNOWN 255
#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;
extern int addressfamily;
extern unsigned replaywin;
extern bool localdiscovery;
-extern sockaddr_t localdiscovery_address;
+
+extern bool udp_discovery;
+extern int udp_discovery_keepalive_interval;
+extern int udp_discovery_interval;
+extern int udp_discovery_timeout;
extern listen_socket_t listen_socket[MAXSOCKETS];
extern int listen_sockets;
extern void handle_new_unix_connection(void *, int);
extern int setup_listen_socket(const sockaddr_t *);
extern int setup_vpn_in_socket(const sockaddr_t *);
-extern bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len);
-extern bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len);
+extern bool send_sptps_data(void *handle, uint8_t 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 *, vpn_packet_t *);
extern void receive_tcppacket(struct connection_t *, const char *, int);
extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
extern bool node_read_ecdsa_public_key(struct node_t *);
extern bool read_ecdsa_public_key(struct connection_t *);
extern bool read_rsa_public_key(struct connection_t *);
-extern void send_mtu_probe(struct node_t *);
extern void handle_device_data(void *, int);
extern void handle_meta_connection_data(struct connection_t *);
extern void regenerate_key(void);
extern int reload_configuration(void);
extern void load_all_subnets(void);
extern void load_all_nodes(void);
+extern void try_tx(struct node_t *n, bool);
#ifndef HAVE_MINGW
#define closesocket(s) close(s)
-#else
-extern CRITICAL_SECTION mutex;
#endif
#endif /* __TINC_NET_H__ */
/*
net_packet.c -- Handles in- and outgoing VPN packets
Copyright (C) 1998-2005 Ivo Timmermans,
- 2000-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2000-2014 Guus Sliepen <guus@tinc-vpn.org>
2010 Timothy Redaelli <timothy@redaelli.eu>
2010 Brandon Black <blblack@gmail.com>
#include "digest.h"
#include "device.h"
#include "ethernet.h"
+#include "ipv4.h"
+#include "ipv6.h"
#include "graph.h"
#include "logger.h"
#include "net.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;
#ifdef HAVE_LZO
static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999_MEM_COMPRESS : LZO1X_1_MEM_COMPRESS];
static void send_udppacket(node_t *, vpn_packet_t *);
unsigned replaywin = 16;
-bool localdiscovery = false;
-sockaddr_t localdiscovery_address;
+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.
-
-*/
-
-static void send_mtu_probe_handler(void *data) {
- node_t *n = data;
- int timeout = 1;
-
- n->mtuprobes++;
-
- if(!n->status.reachable || !n->status.validkey) {
- logger(DEBUG_TRAFFIC, 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;
- }
- logger(DEBUG_TRAFFIC, LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname);
- n->status.udp_confirmed = false;
- n->mtuprobes = 1;
- n->minmtu = 0;
- n->maxmtu = MTU;
- }
-
- if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) {
- logger(DEBUG_TRAFFIC, 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
n->maxmtu = n->minmtu;
n->mtu = n->minmtu;
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 = 31;
- }
-
- if(n->mtuprobes == 31) {
- timeout = pinginterval;
- goto end;
- } else if(n->mtuprobes == 32) {
- timeout = pingtimeout;
+ n->mtuprobes = -1;
}
+}
- for(int i = 0; i < 4 + localdiscovery; i++) {
- int len;
-
- 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;
-
- vpn_packet_t packet;
- memset(packet.data, 0, 14);
- randomize(packet.data + 14, len - 14);
- packet.len = len;
- packet.priority = 0;
- n->status.broadcast = i >= 4 && n->mtuprobes <= 10 && n->prevedge;
+static void udp_probe_timeout_handler(void *data) {
+ node_t *n = data;
+ if(!n->status.udp_confirmed)
+ return;
- logger(DEBUG_TRAFFIC, LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname);
+ 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->maxrecentlen = 0;
+ n->mtuprobes = 0;
+ n->minmtu = 0;
+ n->maxmtu = MTU;
+}
- send_udppacket(n, &packet);
+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;
}
- n->status.broadcast = false;
- n->probe_counter = 0;
- gettimeofday(&n->probe_time, NULL);
-
- /* Calculate the packet loss of incoming traffic by comparing the rate of
- packets received to the rate with which the sequence number has increased.
- */
+ /* 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(n->received > n->prev_received)
- n->packetloss = 1.0 - (n->received - n->prev_received) / (float)(n->received_seqno - n->prev_received_seqno);
- else
- n->packetloss = n->received_seqno <= n->prev_received_seqno;
-
- n->prev_received_seqno = n->received_seqno;
- n->prev_received = n->received;
+ } else {
+ /* 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);
+ }
-end:
- timeout_set(&n->mtutimeout, &(struct timeval){timeout, rand() % 100000});
-}
+ /* Temporarily set udp_confirmed, so that the reply is sent
+ back exactly the way it came in. */
-void send_mtu_probe(node_t *n) {
- timeout_add(&n->mtutimeout, send_mtu_probe_handler, n, &(struct timeval){1, 0});
- send_mtu_probe_handler(n);
+ bool udp_confirmed = n->status.udp_confirmed;
+ n->status.udp_confirmed = true;
+ send_udppacket(n, packet);
+ n->status.udp_confirmed = udp_confirmed;
}
-static void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
- if(!packet->data[0]) {
- logger(DEBUG_TRAFFIC, LOG_INFO, "Got MTU probe request %d from %s (%s)", packet->len, n->name, n->hostname);
-
- /* It's a probe request, send back a reply */
-
- /* Type 2 probe replies were introduced in protocol 17.3 */
- if ((n->options >> 24) == 3) {
- uint8_t* data = packet->data;
- *data++ = 2;
- uint16_t len16 = htons(len); memcpy(data, &len16, 2); data += 2;
- struct timeval now;
- gettimeofday(&now, NULL);
- uint32_t sec = htonl(now.tv_sec); memcpy(data, &sec, 4); data += 4;
- uint32_t usec = htonl(now.tv_usec); memcpy(data, &usec, 4); data += 4;
- packet->len = data - packet->data;
- } else {
- /* Legacy protocol: n won't understand type 2 probe replies. */
- packet->data[0] = 1;
- }
+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);
+ return send_udp_probe_reply(n, packet, len);
+ }
- /* Temporarily set udp_confirmed, so that the reply is sent
- back exactly the way it came in. */
+ 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);
+ }
- bool udp_confirmed = n->status.udp_confirmed;
- n->status.udp_confirmed = true;
- send_udppacket(n, packet);
- n->status.udp_confirmed = udp_confirmed;
- } else {
- length_t probelen = len;
- if (packet->data[0] == 2) {
- if (len < 3)
- logger(DEBUG_TRAFFIC, LOG_WARNING, "Received invalid (too short) MTU probe reply from %s (%s)", n->name, n->hostname);
- else {
- uint16_t probelen16; memcpy(&probelen16, packet->data + 1, 2); probelen = ntohs(probelen16);
- }
- }
- logger(DEBUG_TRAFFIC, LOG_INFO, "Got type %d MTU probe reply %d from %s (%s)", packet->data[0], probelen, n->name, n->hostname);
+ 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. */
+ /* It's a valid reply: now we know bidirectional communication
+ is possible using the address and socket that the reply
+ packet used. */
+ n->status.udp_confirmed = true;
- n->status.udp_confirmed = true;
+ // Reset the UDP ping timer.
+ n->udp_ping_sent = now;
- /* If we haven't established the PMTU yet, restart the discovery process. */
+ 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(n->mtuprobes > 30) {
- if (probelen == n->maxmtu + 8) {
- logger(DEBUG_TRAFFIC, LOG_INFO, "Increase in PMTU to %s (%s) detected, restarting PMTU discovery", n->name, n->hostname);
- n->maxmtu = MTU;
- n->mtuprobes = 10;
- return;
- }
+ 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(n->minmtu)
- n->mtuprobes = 30;
- else
- n->mtuprobes = 1;
- }
+ /* If applicable, raise the minimum supported MTU */
- /* If applicable, raise the minimum supported MTU */
-
- if(probelen > n->maxmtu)
- probelen = n->maxmtu;
- if(n->minmtu < probelen)
- n->minmtu = probelen;
-
- /* Calculate RTT and bandwidth.
- The RTT is the time between the MTU probe burst was sent and the first
- reply is received. The bandwidth is measured using the time between the
- arrival of the first and third probe reply (or type 2 probe requests).
- */
-
- struct timeval now, diff;
- gettimeofday(&now, NULL);
- timersub(&now, &n->probe_time, &diff);
-
- struct timeval probe_timestamp = now;
- if (packet->data[0] == 2 && packet->len >= 11) {
- uint32_t sec; memcpy(&sec, packet->data + 3, 4);
- uint32_t usec; memcpy(&usec, packet->data + 7, 4);
- probe_timestamp.tv_sec = ntohl(sec);
- probe_timestamp.tv_usec = ntohl(usec);
- }
-
- n->probe_counter++;
-
- if(n->probe_counter == 1) {
- n->rtt = diff.tv_sec + diff.tv_usec * 1e-6;
- n->probe_time = probe_timestamp;
- } else if(n->probe_counter == 3) {
- struct timeval probe_timestamp_diff;
- timersub(&probe_timestamp, &n->probe_time, &probe_timestamp_diff);
- n->bandwidth = 2.0 * probelen / (probe_timestamp_diff.tv_sec + probe_timestamp_diff.tv_usec * 1e-6);
- logger(DEBUG_TRAFFIC, LOG_DEBUG, "%s (%s) RTT %.2f ms, burst bandwidth %.3f Mbit/s, rx packet loss %.2f %%", n->name, n->hostname, n->rtt * 1e3, n->bandwidth * 8e-6, n->packetloss * 1e2);
- }
+ if(n->minmtu < len) {
+ n->minmtu = len;
+ try_fix_mtu(n);
}
}
static bool try_mac(node_t *n, const vpn_packet_t *inpkt) {
if(n->status.sptps)
- return sptps_verify_datagram(&n->sptps, (char *)&inpkt->seqno, inpkt->len);
+ return sptps_verify_datagram(&n->sptps, DATA(inpkt), inpkt->len);
- if(!digest_active(n->indigest) || inpkt->len < sizeof inpkt->seqno + digest_length(n->indigest))
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ if(!n->status.validkey_in || !digest_active(n->indigest) || inpkt->len < sizeof(seqno_t) + digest_length(n->indigest))
return false;
- return digest_verify(n->indigest, &inpkt->seqno, inpkt->len - digest_length(n->indigest), (const char *)&inpkt->seqno + inpkt->len - digest_length(n->indigest));
+ return digest_verify(n->indigest, SEQNO(inpkt), inpkt->len - digest_length(n->indigest), DATA(inpkt) + 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) {
vpn_packet_t pkt1, pkt2;
vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 };
int nextpkt = 0;
- vpn_packet_t *outpkt = pkt[0];
size_t outlen;
+ pkt1.offset = DEFAULT_PACKET_OFFSET;
+ pkt2.offset = DEFAULT_PACKET_OFFSET;
if(n->status.sptps) {
if(!n->sptps.state) {
} else {
logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname);
}
- return;
+ return false;
}
- sptps_receive_data(&n->sptps, (char *)&inpkt->seqno, inpkt->len);
- return;
+ inpkt->offset += 2 * sizeof(node_id_t);
+ n->status.udppacket = true;
+ bool result = sptps_receive_data(&n->sptps, DATA(inpkt), inpkt->len - 2 * sizeof(node_id_t));
+ n->status.udppacket = false;
+
+ if(!result) {
+ logger(DEBUG_TRAFFIC, LOG_ERR, "Got bad packet from %s (%s)", n->name, n->hostname);
+ return false;
+ }
+ return true;
}
- if(!n->status.validkey) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ 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;
+ return false;
}
/* Check packet length */
- if(inpkt->len < sizeof inpkt->seqno + digest_length(n->indigest)) {
+ if(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;
+ 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(digest_active(n->indigest)) {
inpkt->len -= digest_length(n->indigest);
- if(!digest_verify(n->indigest, &inpkt->seqno, inpkt->len, (const char *)&inpkt->seqno + inpkt->len)) {
+ 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;
+ return false;
}
}
/* Decrypt the packet */
if(cipher_active(n->incipher)) {
- outpkt = pkt[nextpkt++];
+ vpn_packet_t *outpkt = pkt[nextpkt++];
outlen = MAXSIZE;
- if(!cipher_decrypt(n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+ 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;
+ return false;
}
outpkt->len = outlen;
/* 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) {
logger(DEBUG_ALWAYS, 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;
+ n->name, n->hostname, seqno - n->received_seqno - 1, n->farfuture);
+ return false;
}
logger(DEBUG_ALWAYS, LOG_WARNING, "Lost %d packets from %s (%s)",
- inpkt->seqno - n->received_seqno - 1, n->name, n->hostname);
+ 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))) {
+ } 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_ALWAYS, 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;
+ n->name, n->hostname, seqno, n->received_seqno);
+ return false;
}
} else {
- for(int i = n->received_seqno + 1; i < inpkt->seqno; i++)
+ for(int 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++;
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)) < 0) {
+ if((outpkt->len = uncompress_packet(DATA(outpkt), DATA(inpkt), inpkt->len, n->incompression)) < 0) {
logger(DEBUG_TRAFFIC, LOG_ERR, "Error while uncompressing packet from %s (%s)",
n->name, n->hostname);
- return;
+ return false;
}
inpkt = outpkt;
origlen -= MTU/64 + 20;
}
+ 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, int len) {
vpn_packet_t outpkt;
+ outpkt.offset = DEFAULT_PACKET_OFFSET;
- if(len > sizeof outpkt.data)
+ if(len > sizeof outpkt.data - outpkt.offset)
return;
outpkt.len = len;
outpkt.priority = 0;
else
outpkt.priority = -1;
- memcpy(outpkt.data, buffer, len);
+ memcpy(DATA(&outpkt), buffer, len);
receive_packet(c->node, &outpkt);
}
static void send_sptps_packet(node_t *n, vpn_packet_t *origpkt) {
- if(!n->status.validkey) {
- 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);
- }
+ if(!n->status.validkey && !n->connection)
return;
- }
uint8_t type = 0;
int offset = 0;
- if(!(origpkt->data[12] | origpkt->data[13])) {
- sptps_send_record(&n->sptps, PKT_PROBE, (char *)origpkt->data, origpkt->len);
+ if(!(DATA(origpkt)[12] | DATA(origpkt)[13])) {
+ sptps_send_record(&n->sptps, PKT_PROBE, (char *)DATA(origpkt), origpkt->len);
return;
}
vpn_packet_t outpkt;
if(n->outcompression) {
- int len = compress_packet(outpkt.data + offset, origpkt->data + offset, origpkt->len - offset, n->outcompression);
+ outpkt.offset = 0;
+ int len = compress_packet(DATA(&outpkt) + offset, DATA(origpkt) + offset, origpkt->len - offset, n->outcompression);
if(len < 0) {
logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", n->name, n->hostname);
} else if(len < origpkt->len - offset) {
}
}
- sptps_send_record(&n->sptps, type, (char *)origpkt->data + offset, origpkt->len - offset);
+ /* 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 = rand() % listen_sockets;
}
- /* 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;
- }
- }
- }
+ adapt_socket(*sa, sock);
}
-static void choose_broadcast_address(const node_t *n, const sockaddr_t **sa, int *sock) {
- static sockaddr_t broadcast_ipv4 = {
- .in = {
- .sin_family = AF_INET,
- .sin_addr.s_addr = -1,
- }
- };
-
- static sockaddr_t broadcast_ipv6 = {
- .in6 = {
- .sin6_family = AF_INET6,
- .sin6_addr.s6_addr[0x0] = 0xff,
- .sin6_addr.s6_addr[0x1] = 0x02,
- .sin6_addr.s6_addr[0xf] = 0x01,
- }
- };
+static void choose_local_address(const node_t *n, const sockaddr_t **sa, int *sock) {
+ *sa = NULL;
- *sock = rand() % listen_sockets;
+ /* Pick one of the edges from this node at random, then use its local address. */
- if(listen_socket[*sock].sa.sa.sa_family == AF_INET6) {
- if(localdiscovery_address.sa.sa_family == AF_INET6) {
- localdiscovery_address.in6.sin6_port = n->prevedge->address.in.sin_port;
- *sa = &localdiscovery_address;
- } else {
- broadcast_ipv6.in6.sin6_port = n->prevedge->address.in.sin_port;
- broadcast_ipv6.in6.sin6_scope_id = listen_socket[*sock].sa.in6.sin6_scope_id;
- *sa = &broadcast_ipv6;
- }
- } else {
- if(localdiscovery_address.sa.sa_family == AF_INET) {
- localdiscovery_address.in.sin_port = n->prevedge->address.in.sin_port;
- *sa = &localdiscovery_address;
- } else {
- broadcast_ipv4.in.sin_port = n->prevedge->address.in.sin_port;
- *sa = &broadcast_ipv4;
+ 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) {
size_t outlen;
#if defined(SOL_IP) && defined(IP_TOS)
static int priority = 0;
-#endif
int origpriority = origpkt->priority;
+#endif
+
+ pkt1.offset = DEFAULT_PACKET_OFFSET;
+ pkt2.offset = DEFAULT_PACKET_OFFSET;
if(!n->status.reachable) {
logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
if(n->status.sptps)
return send_sptps_packet(n, origpkt);
+#ifdef DISABLE_LEGACY
+ return;
+#else
/* Make sure we have a valid key */
if(!n->status.validkey) {
logger(DEBUG_TRAFFIC, LOG_INFO,
"No valid key known yet for %s (%s), forwarding via TCP",
n->name, n->hostname);
-
- if(n->last_req_key + 10 <= now.tv_sec) {
- send_req_key(n);
- n->last_req_key = now.tv_sec;
- }
-
send_tcppacket(n->nexthop->connection, origpkt);
-
return;
}
- if(n->options & OPTION_PMTU_DISCOVERY && inpkt->len > n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
+ 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->outcompression) {
outpkt = pkt[nextpkt++];
- if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) {
+ if((outpkt->len = compress_packet(DATA(outpkt), DATA(inpkt), inpkt->len, n->outcompression)) < 0) {
logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)",
n->name, n->hostname);
return;
/* 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 */
outpkt = pkt[nextpkt++];
outlen = MAXSIZE;
- if(!cipher_encrypt(n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+ 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;
}
/* Add the message authentication code */
if(digest_active(n->outdigest)) {
- if(!digest_create(n->outdigest, &inpkt->seqno, inpkt->len, (char *)&inpkt->seqno + inpkt->len)) {
+ 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;
}
/* Send the packet */
- const sockaddr_t *sa;
+ const sockaddr_t *sa = NULL;
int sock;
- if(n->status.broadcast)
- choose_broadcast_address(n, &sa, &sock);
- else
+ if(n->status.send_locally)
+ choose_local_address(n, &sa, &sock);
+ if(!sa)
choose_udp_address(n, &sa, &sock);
#if defined(SOL_IP) && defined(IP_TOS)
priority = origpriority;
logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting outgoing packet priority to %d", priority);
if(setsockopt(listen_socket[n->sock].udp.fd, SOL_IP, IP_TOS, &priority, sizeof(priority))) /* SO_PRIORITY doesn't seem to work */
- logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno));
}
#endif
- if(sendto(listen_socket[sock].udp.fd, (char *) &inpkt->seqno, inpkt->len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) {
+ if(sendto(listen_socket[sock].udp.fd, SEQNO(inpkt), inpkt->len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) {
if(sockmsgsize(sockerrno)) {
if(n->maxmtu >= origlen)
n->maxmtu = origlen - 1;
if(n->mtu >= origlen)
n->mtu = origlen - 1;
+ try_fix_mtu(n);
} else
logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno));
}
end:
origpkt->len = origlen;
+#endif
}
-bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) {
- node_t *to = handle;
+static bool send_sptps_data_priv(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, or this packet is larger than the MTU. */
+ /* 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.
+ TODO: When relaying, the original sender does not know the end-to-end PMTU (it only knows the PMTU of the first hop).
+ This can lead to scenarios where large packets are sent over UDP to relay, but then relay has no choice but fall back to TCP. */
- if(type >= SPTPS_HANDSHAKE || ((myself->options | to->options) & OPTION_TCPONLY) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > to->minmtu)) {
+ if(type == SPTPS_HANDSHAKE || tcponly || (!direct && !relay_supported) || (type != PKT_PROBE && (len - SPTPS_DATAGRAM_OVERHEAD) > relay->minmtu)) {
char buf[len * 4 / 3 + 5];
b64encode(data, buf, len);
/* If no valid key is known yet, send the packets using ANS_KEY requests,
to ensure we get to learn the reflexive UDP address. */
- if(!to->status.validkey) {
+ if(from == myself && !to->status.validkey) {
to->incompression = myself->incompression;
- return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, myself->name, to->name, buf, to->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, myself->name, to->name, REQ_SPTPS, buf);
+ return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, from->name, to->name, REQ_SPTPS, buf);
}
}
- /* Otherwise, send the packet via UDP */
-
- const sockaddr_t *sa;
- int sock;
+ 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 = {};
+ 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;
- if(to->status.broadcast)
- choose_broadcast_address(to, &sa, &sock);
- else
- choose_udp_address(to, &sa, &sock);
+ }
+ /* 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;
- if(sendto(listen_socket[sock].udp.fd, data, len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) {
+ 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)", 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(to->maxmtu >= len)
- to->maxmtu = len - 1;
- if(to->mtu >= len)
- to->mtu = len - 1;
+ 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", to->name, to->hostname, sockstrerror(sockerrno));
+ 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 char *data, uint16_t len) {
+bool send_sptps_data(void *handle, uint8_t type, const void *data, size_t len) {
+ return send_sptps_data_priv(handle, myself, type, data, len);
+}
+
+bool receive_sptps_record(void *handle, uint8_t type, const void *data, uint16_t len) {
node_t *from = handle;
if(type == SPTPS_HANDSHAKE) {
}
vpn_packet_t inpkt;
+ inpkt.offset = DEFAULT_PACKET_OFFSET;
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(inpkt.data, data, len);
- mtu_probe_h(from, &inpkt, len);
+ memcpy(DATA(&inpkt), data, len);
+ if(inpkt.len > from->maxrecentlen)
+ from->maxrecentlen = inpkt.len;
+ udp_probe_h(from, &inpkt, len);
return true;
}
int offset = (type & PKT_MAC) ? 0 : 14;
if(type & PKT_COMPRESSED) {
- length_t ulen = uncompress_packet(inpkt.data + offset, (const uint8_t *)data, len, from->incompression);
+ length_t ulen = uncompress_packet(DATA(&inpkt) + offset, (const uint8_t *)data, len, from->incompression);
if(ulen < 0) {
return false;
} else {
if(inpkt.len > MAXSIZE)
abort();
} else {
- memcpy(inpkt.data + offset, data, len);
+ memcpy(DATA(&inpkt) + offset, data, len);
inpkt.len = len + offset;
}
/* Generate the Ethernet packet type if necessary */
if(offset) {
- switch(inpkt.data[14] >> 4) {
+ switch(DATA(&inpkt)[14] >> 4) {
case 4:
- inpkt.data[12] = 0x08;
- inpkt.data[13] = 0x00;
+ DATA(&inpkt)[12] = 0x08;
+ DATA(&inpkt)[13] = 0x00;
break;
case 6:
- inpkt.data[12] = 0x86;
- inpkt.data[13] = 0xDD;
+ 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)",
- inpkt.data[14] >> 4, from->name, from->hostname);
+ 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;
}
-/*
- send a packet to the given vpn ip.
+// 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) {
+ send_udp_probe_packet(n, MIN_PROBE_SIZE);
+ n->udp_ping_sent = now;
+
+ 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
+
+ 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.
*/
+
+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 the static relay doesn't support SPTPS, everything goes via TCP anyway. */
+
+ if((via->options >> 24) < 4)
+ return;
+
+ /* If we do have a static relay, try everything with that one instead. */
+
+ if(via != n)
+ try_tx_sptps(via, mtu);
+
+ /* 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_sptps(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.sptps)
+ try_tx_sptps(n, mtu);
+ else
+ try_tx_legacy(n, mtu);
+}
+
void send_packet(node_t *n, vpn_packet_t *packet) {
- node_t *via;
+ // 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);
n->out_packets++;
n->out_bytes += packet->len;
devops.write(packet);
return;
}
- logger(DEBUG_TRAFFIC, 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) {
- logger(DEBUG_TRAFFIC, 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;
}
+ // Keep track of packet statistics.
+
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_sptps(n, true);
return;
}
- via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via;
+ // 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);
+ 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;
+ }
-/* Broadcast a packet using the minimum spanning tree */
+ send_udppacket(via, packet);
+ try_tx_legacy(via, true);
+}
void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
// Always give ourself a copy of the packet.
// usually distributes the sending of broadcast packets over all nodes.
case BMODE_MST:
for list_each(connection_t, c, connection_list)
- if(c->status.active && c->status.mst && c != from->nexthop->connection)
+ if(c->edge && c->status.mst && c != from->nexthop->connection)
send_packet(c->node, packet);
break;
}
}
+/* 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) {
- node_t *n = NULL;
+ node_t *match = NULL;
bool hard = false;
static time_t last_hard_try = 0;
- for splay_each(edge_t, e, edge_weight_tree) {
- if(!e->to->status.reachable || e->to == myself)
+ for splay_each(node_t, n, node_tree) {
+ if(!n->status.reachable || n == myself)
+ continue;
+
+ if((n->status.sptps && !n->sptps.instate) || !n->status.validkey_in)
continue;
- if(sockaddrcmp_noport(from, &e->address)) {
+ 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(e->to, pkt))
+ if(!try_mac(n, pkt))
continue;
- n = e->to;
+ match = n;
break;
}
if(hard)
last_hard_try = now.tv_sec;
- last_hard_try = now.tv_sec;
- return n;
+ return match;
}
void handle_incoming_vpn_data(void *data, int flags) {
listen_socket_t *ls = data;
vpn_packet_t pkt;
char *hostname;
- sockaddr_t from = {{0}};
- socklen_t fromlen = sizeof from;
- node_t *n;
- int len;
+ node_id_t nullid = {};
+ sockaddr_t addr = {};
+ socklen_t addrlen = sizeof addr;
+ node_t *from, *to;
+ bool direct = false;
- len = recvfrom(ls->udp.fd, (char *) &pkt.seqno, MAXSIZE, 0, &from.sa, &fromlen);
+ pkt.offset = 0;
+ int len = recvfrom(ls->udp.fd, DATA(&pkt), MAXSIZE, 0, &addr.sa, &addrlen);
if(len <= 0 || len > MAXSIZE) {
if(!sockwouldblock(sockerrno))
pkt.len = len;
- sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */
+ sockaddrunmap(&addr); /* Some braindead IPv6 implementations do stupid things. */
+
+ // 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;
+ }
+ }
- n = lookup_node_udp(&from);
+ if(!n) {
+ pkt.offset = 0;
+ n = try_harder(&addr, &pkt);
+ }
+skip_harder:
if(!n) {
- n = try_harder(&from, &pkt);
- if(n)
- update_node_udp(n, &from);
- else if(debug_level >= DEBUG_PROTOCOL) {
- hostname = sockaddr2hostname(&from);
+ if(debug_level >= DEBUG_PROTOCOL) {
+ hostname = sockaddr2hostname(&addr);
logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname);
free(hostname);
+ }
+ return;
+ }
+
+ if(n->status.sptps) {
+ pkt.offset = 2 * sizeof(node_id_t);
+
+ if(!memcmp(DSTID(&pkt), &nullid, sizeof nullid)) {
+ 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;
}
- else
+
+ if(to != myself) {
+ send_sptps_data_priv(to, n, 0, DATA(&pkt), pkt.len - 2 * sizeof(node_id_t));
+ try_tx_sptps(n, true);
return;
+ }
+ } else {
+ direct = true;
+ from = n;
}
- n->sock = ls - listen_socket;
+ pkt.offset = 0;
+ if(!receive_udppacket(from, &pkt))
+ return;
- receive_udppacket(n, &pkt);
+ n->sock = ls - listen_socket;
+ if(direct && sockaddrcmp(&addr, &n->address))
+ update_node_udp(n, &addr);
}
void handle_device_data(void *data, int flags) {
vpn_packet_t packet;
-
+ packet.offset = DEFAULT_PACKET_OFFSET;
packet.priority = 0;
if(devops.read(&packet)) {
#include "xalloc.h"
char *myport;
+static char *myname;
static io_t device_io;
devops_t devops;
bool device_standby = false;
}
c->ecdsa = ecdsa_read_pem_public_key(fp);
- fclose(fp);
- if(!c->ecdsa)
+ 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) {
if(ecdsa_active(c->ecdsa))
return true;
free(fname);
return c->rsa;
}
+#endif
static bool read_ecdsa_private_key(void) {
FILE *fp;
return invitation_key;
}
+#ifndef DISABLE_LEGACY
static bool read_rsa_private_key(void) {
FILE *fp;
char *fname;
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 keypair with `tinc -n %s generate-rsa-keys'.", netname ?: ".");
free(fname);
return false;
}
free(fname);
return myself->connection->rsa;
}
+#endif
static timeout_t keyexpire_timeout;
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;
}
/*
char *get_name(void) {
char *name = NULL;
+ char *returned_name;
get_config_string(lookup_config(config_tree, "Name"), &name);
if(!name)
return NULL;
- if(*name == '$') {
- char *envname = getenv(name + 1);
- char hostname[32] = "";
- if(!envname) {
- if(strcmp(name + 1, "HOST")) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Invalid Name: environment variable %s does not exist\n", name + 1);
- return false;
- }
- if(gethostname(hostname, sizeof hostname) || !*hostname) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not get hostname: %s\n", strerror(errno));
- 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(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!");
- free(name);
- return false;
- }
-
- return name;
+ returned_name = replace_name(name);
+ free(name);
+ return returned_name;
}
bool setup_myself_reloadable(void) {
char *fmode = NULL;
char *bmode = NULL;
char *afname = NULL;
- char *address = NULL;
char *space;
bool choice;
if(myself->options & OPTION_TCPONLY)
myself->options |= OPTION_INDIRECT;
+ 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);
+
get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly);
get_config_bool(lookup_config(config_tree, "LocalDiscovery"), &localdiscovery);
- memset(&localdiscovery_address, 0, sizeof localdiscovery_address);
- if(get_config_string(lookup_config(config_tree, "LocalDiscoveryAddress"), &address)) {
- struct addrinfo *ai = str2addrinfo(address, myport, SOCK_DGRAM);
- free(address);
- if(!ai)
- return false;
- memcpy(&localdiscovery_address, ai->ai_addr, ai->ai_addrlen);
- }
-
-
if(get_config_string(lookup_config(config_tree, "Mode"), &rmode)) {
if(!strcasecmp(rmode, "router"))
routing_mode = RMODE_ROUTER;
free(bmode);
}
+ 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(SOL_IP) || !defined(IP_TOS)
if(priorityinheritance)
logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "PriorityInheritance");
hint.ai_protocol = IPPROTO_TCP;
hint.ai_flags = AI_PASSIVE;
+#ifdef HAVE_DECL_RES_INIT
+ res_init();
+#endif
int err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai);
free(address);
xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
xasprintf(&envp[1], "DEVICE=%s", device ? : "");
xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
- xasprintf(&envp[3], "NAME=%s", myself->name);
+ xasprintf(&envp[3], "NAME=%s", myname);
execute_script("tinc-up", envp);
xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
xasprintf(&envp[1], "DEVICE=%s", device ? : "");
xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
- xasprintf(&envp[3], "NAME=%s", myself->name);
+ xasprintf(&envp[3], "NAME=%s", myname);
execute_script("tinc-down", envp);
return false;
}
+ myname = xstrdup(name);
myself = new_node();
myself->connection = new_connection();
myself->name = name;
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)
return false;
}
- if(!read_rsa_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 */
sptps_replaywin = replaywin;
}
+#ifndef DISABLE_LEGACY
/* Generate packet encryption key */
if(!get_config_string(lookup_config(config_tree, "Cipher"), &cipher))
}
free(digest);
+#endif
/* Compression */
for(int i = 0; i < listen_sockets; i++) {
salen = sizeof sa;
if(getsockname(i + 3, &sa.sa, &salen) < 0) {
- logger(DEBUG_ALWAYS, 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;
}
/* 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.fd, &sa.sa, &salen)) {
if (!device_standby)
device_disable();
- if(myport) free(myport);
+ free(myport);
if (device_fd >= 0)
io_del(&device_io);
- devops.close();
+ if (devops.close)
+ devops.close();
exit_control();
+ free(myname);
+ free(scriptextension);
+ free(scriptinterpreter);
+
return;
}
status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr));
if(status) {
logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface,
- strerror(errno));
+ sockstrerror(sockerrno));
return false;
}
#else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */
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", strerror(errno));
+ logger(DEBUG_CONNECTIONS, LOG_WARNING, "Can't bind outgoing socket: %s", sockstrerror(sockerrno));
return false;
}
if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof ifr)) {
closesocket(nfd);
logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface,
- strerror(sockerrno));
+ sockstrerror(sockerrno));
return -1;
}
#else
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(DEBUG_ALWAYS, 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(DEBUG_ALWAYS, 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(IPPROTO_IPV6) && defined(IPV6_V6ONLY)
if(sa->sa.sa_family == AF_INET6)
int fd[2];
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s", strerror(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s", sockstrerror(sockerrno));
return;
}
ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0);
if(outlen <= 0) {
- if(!errno || errno == EPIPE) {
+ if(!sockerrno || sockerrno == EPIPE) {
logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", c->name, c->hostname);
} else if(sockwouldblock(sockerrno)) {
logger(DEBUG_CONNECTIONS, 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, strerror(errno));
+ 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->status.active);
+ terminate_connection(c, c->edge);
return;
}
connection_t *c = data;
if(c->status.connecting) {
- c->status.connecting = false;
-
- int result;
- socklen_t len = sizeof result;
- getsockopt(c->socket, SOL_SOCKET, SO_ERROR, (void *)&result, &len);
-
- if(!result)
- finish_connecting(c);
- else {
- logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Error while connecting to %s (%s): %s", c->name, c->hostname, sockstrerror(result));
- terminate_connection(c, false);
+ /*
+ 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);
+ }
return;
}
+
+ c->status.connecting = false;
+ finish_connecting(c);
}
if(flags & IO_WRITE)
c->status.connecting = true;
c->name = xstrdup(outgoing->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;
c->last_ping_time = now.tv_sec;
c = new_connection();
c->name = xstrdup("<unknown>");
+#ifndef DISABLE_LEGACY
c->outcipher = myself->connection->outcipher;
c->outdigest = myself->connection->outdigest;
+#endif
c->outmaclength = myself->connection->outmaclength;
c->outcompression = myself->connection->outcompression;
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->status.active);
+ terminate_connection(c, c->edge);
}
}
hint.ai_family = addressfamily;
hint.ai_socktype = socktype;
+#ifdef HAVE_DECL_RES_INIT
+ res_init();
+#endif
err = getaddrinfo(address, service, &hint, &ai);
if(err) {
#include "utils.h"
#include "xalloc.h"
+#include "ed25519/sha512.h"
+
splay_tree_t *node_tree;
+static splay_tree_t *node_id_tree;
static hash_t *node_udp_cache;
+static hash_t *node_id_cache;
node_t *myself;
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));
+}
+
void init_nodes(void) {
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_cache = hash_alloc(0x100, sizeof(sockaddr_t));
+ node_id_cache = hash_alloc(0x100, sizeof(node_id_t));
}
void exit_nodes(void) {
+ hash_free(node_id_cache);
hash_free(node_udp_cache);
+ splay_delete_tree(node_id_tree);
splay_delete_tree(node_tree);
}
sockaddrfree(&n->address);
+#ifndef DISABLE_LEGACY
cipher_close(n->incipher);
digest_close(n->indigest);
cipher_close(n->outcipher);
digest_close(n->outdigest);
+#endif
ecdsa_free(n->ecdsa);
sptps_stop(&n->sptps);
- timeout_del(&n->mtutimeout);
+ timeout_del(&n->udp_ping_timeout);
if(n->hostname)
free(n->hostname);
}
void node_add(node_t *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) {
+ hash_delete(node_udp_cache, &n->address);
+ hash_delete(node_id_cache, &n->id);
+
for splay_each(subnet_t, s, n->subnet_tree)
subnet_del(n, s);
for splay_each(edge_t, e, n->edge_tree)
edge_del(e);
+ splay_delete(node_id_tree, n);
splay_delete(node_tree, n);
}
return splay_search(node_tree, &n);
}
+node_t *lookup_node_id(const node_id_t *id) {
+ node_t *n = hash_search(node_id_cache, id);
+ if(!n) {
+ node_t tmp = {.id = *id};
+ n = splay_search(node_id_tree, &tmp);
+ if(n)
+ hash_insert(node_id_cache, id, n);
+ }
+
+ return n;
+}
+
node_t *lookup_node_udp(const sockaddr_t *sa) {
return hash_search(node_udp_cache, sa);
}
return;
}
- hash_insert(node_udp_cache, &n->address, NULL);
+ hash_delete(node_udp_cache, &n->address);
if(sa) {
n->address = *sa;
n->hostname = sockaddr2hostname(&n->address);
logger(DEBUG_PROTOCOL, LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname);
}
+
+ /* 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)
- send_request(c, "%d %d %s %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", CONTROL, REQ_DUMP_NODES,
- n->name, n->hostname ?: "unknown port unknown", cipher_get_nid(n->outcipher),
- digest_get_nid(n->outdigest), (int)digest_length(n->outdigest), n->outcompression,
- n->options, bitfield_to_int(&n->status, sizeof n->status), n->nexthop ? n->nexthop->name : "-",
- n->via ? n->via->name ?: "-" : "-", n->distance, n->mtu, n->minmtu, n->maxmtu, (long)n->last_state_change);
+ 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)
+ sprintf(id + 2 * c, "%02hhx", 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 %hd %hd %hd %ld", CONTROL, REQ_DUMP_NODES,
+ n->name, id, 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->distance,
+ n->mtu, n->minmtu, n->maxmtu, (long)n->last_state_change);
+ }
return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES);
}
unsigned int indirect:1; /* 1 if this node is not directly reachable by us */
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 broadcast:1; /* 1 if the next UDP packet should be broadcast to the local network */
- unsigned int unused:23;
+ 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 if we have sent a valid key to him */
+ unsigned int unused:22;
} node_status_t;
typedef struct node_t {
char *name; /* name of this node */
+ 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 */
ecdsa_t *ecdsa; /* His public ECDSA key */
sptps_t sptps;
+#ifndef DISABLE_LEGACY
cipher_t *incipher; /* Cipher for UDP packets */
digest_t *indigest; /* Digest for UDP packets */
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 */
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 */
+ timeout_t udp_ping_timeout; /* Ping timeout event */
+
+ struct timeval mtu_ping_sent; /* Last time a MTU probe 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 */
- timeout_t mtutimeout; /* Probe event */
- struct timeval probe_time; /* Time the last probe was sent or received */
- int probe_counter; /* Number of probes received since last burst was sent */
- float rtt; /* Last measured round trip time */
- float bandwidth; /* Last measured bandwidth */
- float packetloss; /* Last measured packet loss rate */
uint64_t in_packets;
uint64_t in_bytes;
extern void node_add(node_t *);
extern void node_del(node_t *);
extern node_t *lookup_node(char *);
+extern node_t *lookup_node_id(const node_id_t *);
extern node_t *lookup_node_udp(const sockaddr_t *);
extern bool dump_nodes(struct connection_t *);
extern bool dump_traffic(struct connection_t *);
--- /dev/null
+/*
+ crypto.c -- Cryptographic miscellaneous functions and initialisation
+ Copyright (C) 2007-2014 Guus Sliepen <guus@tinc-vpn.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "../system.h"
+
+#include <openssl/rand.h>
+#include <openssl/evp.h>
+#include <openssl/engine.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 *out, size_t outlen) {
+ while(outlen) {
+ size_t len = read(random_fd, out, outlen);
+ if(len <= 0) {
+ if(errno == EAGAIN || errno == EINTR)
+ continue;
+ fprintf(stderr, "Could not read random numbers: %s\n", strerror(errno));
+ abort();
+ }
+ out += len;
+ outlen -= len;
+ }
+}
+
+#else
+
+#include <wincrypt.h>
+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();
+}
--- /dev/null
+/*
+ prf.c -- Pseudo-Random Function for key material generation
+ Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "../system.h"
+
+#include "../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 bool hmac_sha512(const char *key, size_t keylen, const char *msg, size_t msglen, char *out) {
+ char tmp[2 * mdlen];
+ sha512_context md;
+
+ if(keylen <= mdlen) {
+ memcpy(tmp, key, keylen);
+ memset(tmp + keylen, 0, mdlen - keylen);
+ } else {
+ if(sha512(key, keylen, tmp) != 0)
+ return false;
+ }
+
+ if(sha512_init(&md) != 0)
+ return false;
+
+ // ipad
+ memxor(tmp, 0x36, mdlen);
+ if(sha512_update(&md, tmp, mdlen) != 0)
+ return false;
+
+ // message
+ if(sha512_update(&md, msg, msglen) != 0)
+ return false;
+
+ if(sha512_final(&md, tmp + mdlen) != 0)
+ return false;
+
+ // opad
+ memxor(tmp, 0x36 ^ 0x5c, mdlen);
+ 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(data, sizeof data, secret, secretlen, data))
+ return false;
+
+ /* Outer HMAC */
+ if(outlen >= mdlen) {
+ if(!hmac_sha512(data, sizeof data, secret, secretlen, out))
+ return false;
+ out += mdlen;
+ outlen -= mdlen;
+ } else {
+ if(!hmac_sha512(data, sizeof data, secret, secretlen, hash))
+ return false;
+ memcpy(out, hash, outlen);
+ out += outlen;
+ outlen = 0;
+ }
+ }
+
+ return true;
+}
struct cipher {
EVP_CIPHER_CTX ctx;
const EVP_CIPHER *cipher;
- struct cipher_counter *counter;
};
-typedef struct cipher_counter {
- unsigned char counter[CIPHER_MAX_IV_SIZE];
- unsigned char block[CIPHER_MAX_IV_SIZE];
- int n;
-} cipher_counter_t;
-
static cipher_t *cipher_open(const EVP_CIPHER *evp_cipher) {
cipher_t *cipher = xzalloc(sizeof *cipher);
cipher->cipher = evp_cipher;
return;
EVP_CIPHER_CTX_cleanup(&cipher->ctx);
- free(cipher->counter);
free(cipher);
}
return cipher->cipher->key_len + cipher->cipher->iv_len;
}
+size_t cipher_blocksize(const cipher_t *cipher) {
+ if(!cipher || !cipher->cipher)
+ return 1;
+
+ return cipher->cipher->block_size;
+}
+
bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) {
bool result;
return false;
}
-bool cipher_set_counter(cipher_t *cipher, const void *counter, size_t len) {
- if(len > cipher->cipher->iv_len - 4) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Counter too long");
- return false;
- }
-
- memcpy(cipher->counter->counter, counter, len);
- cipher->counter->n = 0;
-
- return true;
-}
-
-bool cipher_set_counter_key(cipher_t *cipher, void *key) {
- int result = EVP_EncryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, NULL);
- if(!result) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- if(!cipher->counter)
- cipher->counter = xzalloc(sizeof *cipher->counter);
- else
- cipher->counter->n = 0;
-
- memcpy(cipher->counter->counter, (unsigned char *)key + cipher->cipher->key_len, cipher->cipher->iv_len);
-
- return true;
-}
-
-bool cipher_gcm_encrypt_start(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- int len = 0;
- if(!EVP_EncryptInit_ex(&cipher->ctx, NULL, NULL, NULL, cipher->counter->counter)
- || (inlen && !EVP_EncryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen))) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- if(outlen)
- *outlen = len;
- return true;
-}
-
-bool cipher_gcm_encrypt_finish(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- int len = 0, pad = 0;
- if((inlen && !EVP_EncryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen))
- || !EVP_EncryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- EVP_CIPHER_CTX_ctrl(&cipher->ctx, EVP_CTRL_GCM_GET_TAG, 16, (unsigned char *)outdata + len + pad);
- if(outlen)
- *outlen = len + pad + 16;
- return true;
-}
-
-bool cipher_gcm_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- int len = 0, pad = 0;
- if(!EVP_EncryptInit_ex(&cipher->ctx, NULL, NULL, NULL, cipher->counter->counter) ||
- !EVP_EncryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen) ||
- !EVP_EncryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- EVP_CIPHER_CTX_ctrl(&cipher->ctx, EVP_CTRL_GCM_GET_TAG, 16, (unsigned char *)outdata + len + pad);
- if(outlen)
- *outlen = len + pad + 16;
- return true;
-}
-
-bool cipher_gcm_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- if(inlen < 16)
- return false;
-
- int len = 0, pad = 0;
- if(!EVP_DecryptInit_ex(&cipher->ctx, NULL, NULL, NULL, cipher->counter->counter)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- EVP_CIPHER_CTX_ctrl(&cipher->ctx, EVP_CTRL_GCM_SET_TAG, 16, (unsigned char *)indata + inlen - 16);
-
- if(!EVP_DecryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen - 16) ||
- !EVP_DecryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- if(outlen)
- *outlen = len;
- return true;
-}
-
-bool cipher_gcm_decrypt_start(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- int len = 0;
- if(!EVP_DecryptInit_ex(&cipher->ctx, NULL, NULL, NULL, cipher->counter->counter)
- || (inlen && !EVP_DecryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen))) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- if(outlen)
- *outlen = len;
- return true;
-}
-
-bool cipher_gcm_decrypt_finish(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen) {
- if(inlen < 16)
- return false;
-
- EVP_CIPHER_CTX_ctrl(&cipher->ctx, EVP_CTRL_GCM_SET_TAG, 16, (unsigned char *)indata + inlen - 16);
-
- int len = 0, pad = 0;
- if((inlen > 16 && !EVP_DecryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, (unsigned char *)indata, inlen - 16))
- || !EVP_DecryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
- return true;
-}
-
-
bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) {
if(oneshot) {
int len, pad;
/*
crypto.c -- Cryptographic miscellaneous functions and initialisation
- Copyright (C) 2007-2013 Guus Sliepen <guus@tinc-vpn.org>
+ Copyright (C) 2007-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#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 *out, size_t outlen) {
+ while(outlen) {
+ size_t len = read(random_fd, out, outlen);
+ if(len <= 0) {
+ if(errno == EAGAIN || errno == EINTR)
+ continue;
+ fprintf(stderr, "Could not read random numbers: %s\n", strerror(errno));
+ abort();
+ }
+ out += len;
+ outlen -= len;
+ }
+}
+
+#else
+
+#include <wincrypt.h>
+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) {
- RAND_load_file("/dev/urandom", 1024);
+ random_init();
ENGINE_load_builtin_engines();
ENGINE_register_all_complete();
void crypto_exit(void) {
EVP_cleanup();
-}
-
-void randomize(void *out, size_t outlen) {
- RAND_pseudo_bytes(out, outlen);
+ ERR_free_strings();
+ ENGINE_cleanup();
+ random_exit();
}
+++ /dev/null
-/*
- ecdh.c -- Diffie-Hellman key exchange handling
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "../system.h"
-
-#include <openssl/err.h>
-#include <openssl/ec.h>
-#include <openssl/ecdh.h>
-#include <openssl/obj_mac.h>
-
-#define __TINC_ECDH_INTERNAL__
-typedef EC_KEY ecdh_t;
-
-#include "../ecdh.h"
-#include "../logger.h"
-#include "../utils.h"
-#include "../xalloc.h"
-
-ecdh_t *ecdh_generate_public(void *pubkey) {
- ecdh_t *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1);
- if(!ecdh) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- if(!EC_KEY_generate_key(ecdh)) {
- EC_KEY_free(ecdh);
- logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- const EC_POINT *point = EC_KEY_get0_public_key(ecdh);
- if(!point) {
- EC_KEY_free(ecdh);
- logger(DEBUG_ALWAYS, LOG_ERR, "Getting public key failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- size_t result = EC_POINT_point2oct(EC_KEY_get0_group(ecdh), point, POINT_CONVERSION_COMPRESSED, pubkey, ECDH_SIZE, NULL);
- if(!result) {
- EC_KEY_free(ecdh);
- logger(DEBUG_ALWAYS, LOG_ERR, "Converting EC_POINT to binary failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- return ecdh;
-}
-
-bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) {
- EC_POINT *point = EC_POINT_new(EC_KEY_get0_group(ecdh));
- if(!point) {
- logger(DEBUG_ALWAYS, LOG_ERR, "EC_POINT_new() failed: %s", ERR_error_string(ERR_get_error(), NULL));
- EC_KEY_free(ecdh);
- return false;
- }
-
- int result = EC_POINT_oct2point(EC_KEY_get0_group(ecdh), point, pubkey, ECDH_SIZE, NULL);
- if(!result) {
- EC_POINT_free(point);
- EC_KEY_free(ecdh);
- logger(DEBUG_ALWAYS, LOG_ERR, "Converting binary to EC_POINT failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- result = ECDH_compute_key(shared, ECDH_SIZE, point, ecdh, NULL);
- EC_POINT_free(point);
- EC_KEY_free(ecdh);
-
- if(!result) {
- logger(DEBUG_ALWAYS, LOG_ERR, "Computing Elliptic Curve Diffie-Hellman shared key failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- return true;
-}
-
-void ecdh_free(ecdh_t *ecdh) {
- if(ecdh)
- EC_KEY_free(ecdh);
-}
+++ /dev/null
-/*
- ecdsa.c -- ECDSA key handling
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "../system.h"
-
-#include <openssl/pem.h>
-#include <openssl/err.h>
-
-#define __TINC_ECDSA_INTERNAL__
-typedef EC_KEY 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) {
- ecdsa_t *ecdsa = EC_KEY_new_by_curve_name(NID_secp521r1);
- if(!ecdsa) {
- logger(DEBUG_ALWAYS, LOG_DEBUG, "EC_KEY_new_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return NULL;
- }
-
- int len = strlen(p);
- unsigned char pubkey[len / 4 * 3 + 3];
- const unsigned char *ppubkey = pubkey;
- len = b64decode(p, (char *)pubkey, len);
-
- if(!o2i_ECPublicKey(&ecdsa, &ppubkey, len)) {
- logger(DEBUG_ALWAYS, LOG_DEBUG, "o2i_ECPublicKey failed: %s", ERR_error_string(ERR_get_error(), NULL));
- EC_KEY_free(ecdsa);
- return NULL;
- }
-
- return ecdsa;
-}
-
-char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) {
- unsigned char *pubkey = NULL;
- int len = i2o_ECPublicKey(ecdsa, &pubkey);
-
- char *base64 = xmalloc(len * 4 / 3 + 5);
- b64encode((char *)pubkey, base64, len);
-
- free(pubkey);
-
- return base64;
-}
-
-// Read PEM ECDSA keys
-
-ecdsa_t *ecdsa_read_pem_public_key(FILE *fp) {
- ecdsa_t *ecdsa = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL);
-
- if(!ecdsa)
- logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA public key: %s", ERR_error_string(ERR_get_error(), NULL));
-
- return ecdsa;
-}
-
-ecdsa_t *ecdsa_read_pem_private_key(FILE *fp) {
- ecdsa_t *ecdsa = PEM_read_ECPrivateKey(fp, NULL, NULL, NULL);
-
- if(!ecdsa)
- logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA private key: %s", ERR_error_string(ERR_get_error(), NULL));
-
- return ecdsa;
-}
-
-size_t ecdsa_size(ecdsa_t *ecdsa) {
- return ECDSA_size(ecdsa);
-}
-
-// TODO: standardise output format?
-
-bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t len, void *sig) {
- unsigned int siglen = ECDSA_size(ecdsa);
-
- unsigned char hash[SHA512_DIGEST_LENGTH];
- SHA512(in, len, hash);
-
- memset(sig, 0, siglen);
-
- if(!ECDSA_sign(0, hash, sizeof hash, sig, &siglen, ecdsa)) {
- logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_sign() failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- return true;
-}
-
-bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t len, const void *sig) {
- unsigned int siglen = ECDSA_size(ecdsa);
-
- unsigned char hash[SHA512_DIGEST_LENGTH];
- SHA512(in, len, hash);
-
- if(!ECDSA_verify(0, hash, sizeof hash, sig, siglen, ecdsa)) {
- logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_verify() failed: %s", ERR_error_string(ERR_get_error(), NULL));
- return false;
- }
-
- return true;
-}
-
-bool ecdsa_active(ecdsa_t *ecdsa) {
- return ecdsa;
-}
-
-void ecdsa_free(ecdsa_t *ecdsa) {
- if(ecdsa)
- EC_KEY_free(ecdsa);
-}
+++ /dev/null
-/*
- ecdsagen.c -- ECDSA key generation and export
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "../system.h"
-
-#include <openssl/pem.h>
-#include <openssl/err.h>
-#include <openssl/obj_mac.h>
-
-#define __TINC_ECDSA_INTERNAL__
-typedef EC_KEY ecdsa_t;
-
-#include "../ecdsagen.h"
-#include "../utils.h"
-#include "../xalloc.h"
-
-// Generate ECDSA key
-
-ecdsa_t *ecdsa_generate(void) {
- ecdsa_t *ecdsa = EC_KEY_new_by_curve_name(NID_secp521r1);
-
- if(!ecdsa || !EC_KEY_generate_key(ecdsa)) {
- fprintf(stderr, "Generating EC key failed: %s", ERR_error_string(ERR_get_error(), NULL));
- ecdsa_free(ecdsa);
- return false;
- }
-
- EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE);
- EC_KEY_set_conv_form(ecdsa, POINT_CONVERSION_COMPRESSED);
-
- return ecdsa;
-}
-
-// Write PEM ECDSA keys
-
-bool ecdsa_write_pem_public_key(ecdsa_t *ecdsa, FILE *fp) {
- return PEM_write_EC_PUBKEY(fp, ecdsa);
-}
-
-bool ecdsa_write_pem_private_key(ecdsa_t *ecdsa, FILE *fp) {
- return PEM_write_ECPrivateKey(fp, ecdsa, NULL, NULL, 0, NULL, NULL);
-}
#include "subnet.h"
#include "utils.h"
#include "xalloc.h"
+#include "version.h"
/* If zero, don't detach from the terminal. */
bool do_detach = true;
return true;
}
+io_t stop_io;
+
DWORD WINAPI controlhandler(DWORD request, DWORD type, LPVOID boe, LPVOID bah) {
switch(request) {
case SERVICE_CONTROL_INTERROGATE:
return ERROR_CALL_NOT_IMPLEMENTED;
}
- event_exit();
- status.dwWaitHint = 30000;
+ status.dwWaitHint = 1000;
status.dwCurrentState = SERVICE_STOP_PENDING;
SetServiceStatus(statushandle, &status);
+ if (WSASetEvent(stop_io.event) == FALSE)
+ abort();
return NO_ERROR;
}
openlogger(identname, logmode);
logger(DEBUG_ALWAYS, LOG_NOTICE, "tincd %s (%s %s) starting, debug level %d",
- VERSION, __DATE__, __TIME__, debug_level);
+ VERSION, BUILD_DATE, BUILD_TIME, debug_level);
return true;
}
extern bool kill_other(int);
#ifdef HAVE_MINGW
+extern io_t stop_io;
extern bool init_service(void);
#endif
static splay_tree_t *past_request_tree;
-bool check_id(const char *id) {
- if(!id || !*id)
- return false;
-
- for(; *id; id++)
- if(!isalnum(*id) && *id != '_')
- return false;
-
- return true;
-}
-
/* Generic request routines - takes care of logging and error
detection as well */
/* Protocol version. Different major versions are incompatible. */
#define PROT_MAJOR 17
-#define PROT_MINOR 3 /* Should not exceed 255! */
+#define PROT_MINOR 4 /* Should not exceed 255! */
/* Silly Windows */
extern bool send_request(struct connection_t *, const char *, ...) __attribute__ ((__format__(printf, 2, 3)));
extern void forward_request(struct connection_t *, const char *);
extern bool receive_request(struct connection_t *, const char *);
-extern bool check_id(const char *);
extern void init_requests(void);
extern void exit_requests(void);
#include "utils.h"
#include "xalloc.h"
+#include "ed25519/sha512.h"
+
ecdsa_t *invitation_key = NULL;
static bool send_proxyrequest(connection_t *c) {
return true;
}
-static bool receive_invitation_sptps(void *handle, uint8_t type, const char *data, uint16_t len) {
+static bool receive_invitation_sptps(void *handle, uint8_t type, const void *data, uint16_t len) {
connection_t *c = handle;
if(type == 128)
return false;
// Recover the filename from the cookie and the key
- digest_t *digest = digest_open_by_name("sha256", 18);
- if(!digest)
- abort();
char *fingerprint = ecdsa_get_base64_public_key(invitation_key);
char hashbuf[18 + strlen(fingerprint)];
- char cookie[25];
+ char cookie[64];
memcpy(hashbuf, data, 18);
memcpy(hashbuf + 18, fingerprint, sizeof hashbuf - 18);
- digest_create(digest, hashbuf, sizeof hashbuf, cookie);
+ sha512(hashbuf, sizeof hashbuf, cookie);
b64encode_urlsafe(cookie, cookie, 18);
- digest_close(digest);
free(fingerprint);
char filename[PATH_MAX], usedname[PATH_MAX];
}
if(experimental)
- if(!read_ecdsa_public_key(c))
- return false;
- } else {
- if(c->protocol_minor && !ecdsa_active(c->ecdsa))
- c->protocol_minor = 1;
+ read_ecdsa_public_key(c);
+ /* Ignore failures if no key known yet */
}
+ 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 < 2) {
}
bool send_metakey(connection_t *c) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ 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;
if(!(c->outdigest = digest_open_sha1(-1)))
return false;
- size_t len = rsa_size(c->rsa);
+ const size_t len = rsa_size(c->rsa);
char key[len];
char enckey[len];
char hexkey[2 * len + 1];
c->status.encryptout = true;
return result;
+#endif
}
bool metakey_h(connection_t *c, const char *request) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ if(!myself->connection->rsa)
+ return false;
+
char hexkey[MAX_STRING_SIZE];
int cipher, digest, maclength, compression;
- size_t len = rsa_size(myself->connection->rsa);
+ const size_t len = rsa_size(myself->connection->rsa);
char enckey[len];
char key[len];
c->allow_request = CHALLENGE;
return send_challenge(c);
+#endif
}
bool send_challenge(connection_t *c) {
- size_t len = rsa_size(c->rsa);
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ const size_t len = rsa_size(c->rsa);
char buffer[len * 2 + 1];
if(!c->hischallenge)
/* Send the challenge */
return send_request(c, "%d %s", CHALLENGE, buffer);
+#endif
}
bool challenge_h(connection_t *c, const char *request) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
+ if(!myself->connection->rsa)
+ return false;
+
char buffer[MAX_STRING_SIZE];
- size_t len = rsa_size(myself->connection->rsa);
+ const size_t len = rsa_size(myself->connection->rsa);
size_t digestlen = digest_length(c->indigest);
char digest[digestlen];
c->allow_request = CHAL_REPLY;
return send_request(c, "%d %s", CHAL_REPLY, buffer);
+#endif
}
bool chal_reply_h(connection_t *c, const char *request) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
char hishash[MAX_STRING_SIZE];
if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) {
c->allow_request = ACK;
return send_ack(c);
+#endif
}
static bool send_upgrade(connection_t *c) {
+#ifdef DISABLE_LEGACY
+ return false;
+#else
/* Special case when protocol_minor is 1: the other end is Ed25519 capable,
* but doesn't know our key yet. So send it now. */
bool result = send_request(c, "%d %s", ACK, pubkey);
free(pubkey);
return result;
+#endif
}
bool send_ack(connection_t *c) {
}
if(ecdsa_active(c->ecdsa) || read_ecdsa_public_key(c)) {
- logger(DEBUG_ALWAYS, LOG_INFO, "Already have Ed25519 public key from %s (%s), not upgrading.", c->name, c->hostname);
+ 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;
}
/* Activate this connection */
c->allow_request = ALL;
- c->status.active = true;
logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection with %s (%s) activated", c->name,
c->hostname);
sockaddr2str(&c->address, &hisaddress, NULL);
c->edge->address = str2sockaddr(hisaddress, hisport);
free(hisaddress);
+ 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 {
+ char *local_address;
+ sockaddr2str(&local_sa, &local_address, NULL);
+ c->edge->local_address = str2sockaddr(local_address, myport);
+ free(local_address);
+ }
c->edge->weight = (weight + c->estimated_weight) / 2;
c->edge->connection = c;
c->edge->options = c->options;
bool send_add_edge(connection_t *c, const edge_t *e) {
bool x;
char *address, *port;
+ char *local_address, *local_port;
sockaddr2str(&e->address, &address, &port);
+ sockaddr2str(&e->local_address, &local_address, &local_port);
- x = send_request(c, "%d %x %s %s %s %s %x %d", ADD_EDGE, rand(),
+ 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);
+ e->options, e->weight, local_address, local_port);
+
free(address);
free(port);
+ free(local_address);
+ free(local_port);
return x;
}
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] = "unknown";
+ char port_local[MAX_STRING_SIZE] = "unknown";
+ sockaddr_t address, local_address;
uint32_t options;
int weight;
- if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d",
- from_name, to_name, to_address, to_port, &options, &weight) != 6) {
+ 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;
/* Convert addresses */
address = str2sockaddr(to_address, to_port);
+ 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(e->weight != weight || e->options != options || sockaddrcmp(&e->address, &address) || sockaddrcmp(&e->local_address, &local_address)) {
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);
e->from = from;
e->to = to;
e->address = address;
+ e->local_address = local_address;
e->options = options;
e->weight = weight;
edge_add(e);
/*
protocol_key.c -- handle the meta-protocol, key exchange
Copyright (C) 1999-2005 Ivo Timmermans,
- 2000-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2000-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
/* Immediately send new keys to directly connected nodes to keep UDP mappings alive */
for list_each(connection_t, c, connection_list)
- if(c->status.active && c->node && c->node->status.reachable && !c->node->status.sptps)
+ if(c->edge && c->node && c->node->status.reachable && !c->node->status.sptps)
send_ans_key(c->node);
/* Force key exchange for connections using SPTPS */
return true;
}
-static bool send_initial_sptps_data(void *handle, uint8_t type, const char *data, size_t len) {
+static bool send_initial_sptps_data(void *handle, uint8_t type, const void *data, size_t len) {
node_t *to = handle;
to->sptps.send_data = send_sptps_data;
char buf[len * 4 / 3 + 5];
static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, int reqno) {
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;
}
+ /* TODO: forwarding SPTPS packets in this way is inefficient because we send them over TCP without checking for UDP connectivity */
send_request(to->nexthop->connection, "%s", request);
}
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];
to->received = 0;
if(replaywin) memset(to->late, 0, replaywin);
+ 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,
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, const char *request) {
return send_request(to->nexthop->connection, "%s", request);
}
+#ifndef DISABLE_LEGACY
/* Don't use key material until every check has passed. */
cipher_close(from->outcipher);
digest_close(from->outdigest);
+#endif
from->status.validkey = false;
if(compression < 0 || compression > 11) {
sockaddr_t sa = str2sockaddr(address, port);
update_node_udp(from, &sa);
}
-
- if(from->options & OPTION_PMTU_DISCOVERY && !(from->options & OPTION_TCPONLY))
- send_mtu_probe(from);
}
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) {
update_node_udp(from, &sa);
}
- if(from->options & OPTION_PMTU_DISCOVERY && !(from->options & OPTION_TCPONLY))
- send_mtu_probe(from);
-
return true;
+#endif
}
if(!send_request(c, "%d %hd", PACKET, packet->len))
return false;
- return send_meta(c, (char *)packet->data, packet->len);
+ return send_meta(c, (char *)DATA(packet), packet->len);
}
bool tcppacket_h(connection_t *c, const char *request) {
static bool read_packet(vpn_packet_t *packet) {
int inlen;
- if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
+ 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;
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) {
+ 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;
/* 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)
+ 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;
return;
/* 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;
/* 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;
- uint16_t csum = packet->data[start + 16] << 8 | packet->data[start + 17];
+ uint16_t csum = DATA(packet)[start + 16] << 8 | DATA(packet)[start + 17];
if(oldmss <= newmss)
break;
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;
csum += newmss;
csum ^= 0xffff;
- packet->data[start + 16] = csum >> 8;
- packet->data[start + 17] = csum & 0xff;
+ DATA(packet)[start + 16] = csum >> 8;
+ DATA(packet)[start + 17] = csum & 0xff;
break;
}
}
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);
}
static void age_subnets(void *data) {
}
for list_each(connection_t, c, connection_list)
- if(c->status.active)
+ if(c->edge)
send_del_subnet(c, s);
subnet_del(myself, s);
/* If we don't know this MAC address yet, store it */
if(!subnet) {
- logger(DEBUG_TRAFFIC, LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx",
+ 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]);
/* And tell all other tinc daemons it's our MAC */
for list_each(connection_t, c, connection_list)
- if(c->status.active)
+ if(c->edge)
send_add_subnet(c, subnet);
timeout_add(&age_subnets_timeout, age_subnets, NULL, &(struct timeval){10, rand() % 100000});
/* 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 */
/* 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 */
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;
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;
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 = (dest->mtu - ether_size - ip_size) & ~0x7;
ip_off = ntohs(ip.ip_off);
origf = ip_off & ~IP_OFFMASK;
while(todo) {
len = todo > maxlen ? maxlen : todo;
- memcpy(fragment.data + ether_size + ip_size, offset, len);
+ memcpy(DATA(&fragment) + ether_size + ip_size, offset, len);
todo -= len;
offset += len;
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);
}
}
-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) {
return;
}
+ if (!subnet->owner) {
+ broadcast_packet(source, packet);
+ return;
+ }
+
if(subnet->owner == source) {
logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname);
return;
return route_ipv4_unreachable(source, packet, ether_size, ICMP_DEST_UNREACH, ICMP_NET_ANO);
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 && packet->len > MAX(via->mtu, 590) && via != myself) {
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 {
send_packet(subnet->owner, packet);
}
-static void route_ipv4(node_t *source, vpn_packet_t *packet) {
- if(!checklength(source, packet, ether_size + ip_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)))
- broadcast_packet(source, packet);
- else
- route_ipv4_unicast(source, packet);
-}
-
/* RFC 2463 */
static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_t ether_size, uint8_t type, uint8_t code) {
/* 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 */
/* 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 */
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);
send_packet(source, packet);
}
-static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) {
+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(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;
+ }
+
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) {
return;
}
+ if (!subnet->owner) {
+ broadcast_packet(source, packet);
+ return;
+ }
+
if(subnet->owner == source) {
logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", 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 */
checksum = inet_checksum(&ns, ns_size, checksum);
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) {
/* 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 protocoll 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;
checksum = inet_checksum(&ns, ns_size, checksum);
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)
- broadcast_packet(source, packet);
- else
- route_ipv6_unicast(source, packet);
-}
-
/* RFC 826 */
static void route_arp(node_t *source, vpn_packet_t *packet) {
/* 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(subnet->owner == myself)
return; /* silently ignore */
- 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 */
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_sha, DATA(packet) + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */
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);
}
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) {
broadcast_packet(source, packet);
return;
}
if(forwarding_mode == FMODE_OFF && source != myself && subnet->owner != myself)
return;
- uint16_t type = packet->data[12] << 8 | packet->data[13];
+ uint16_t type = DATA(packet)[12] << 8 | DATA(packet)[13];
if(priorityinheritance && type == ETH_P_IP && packet->len >= ether_size + ip_size)
- packet->priority = packet->data[15];
+ packet->priority = DATA(packet)[15];
// Handle packets larger than PMTU
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 {
len = c->outmaclength;
if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, len))
- send_meta(c, (char *)packet->data, len);
+ send_meta(c, (char *)DATA(packet), len);
}
}
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;
}
if(!checklength(source, packet, ethlen + ip_size))
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;
if(!checklength(source, packet, ethlen + ip6_size))
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;
if(!do_decrement_ttl(source, packet))
return;
- 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:
if(q) {
memcpy(ext, p, q - p);
ext[q - p] = 0;
- *q++;
+ q++;
} else {
strcpy(ext, p);
}
device.c -- Interaction with Solaris tun device
Copyright (C) 2001-2005 Ivo Timmermans,
2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
- 2001-2013 Guus Sliepen <guus@tinc-vpn.org>
+ 2001-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
switch(device_type) {
case DEVICE_TYPE_TUN:
- if((inlen = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
+ if((inlen = read(device_fd, DATA(packet) + 14, MTU - 14)) <= 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:
- logger(DEBUG_TRAFFIC, 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 = inlen + 14;
break;
case DEVICE_TYPE_TAP:
- if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
+ 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;
}
switch(device_type) {
case DEVICE_TYPE_TUN:
- if(write(device_fd, packet->data + 14, packet->len - 14) < 0) {
+ if(write(device_fd, DATA(packet) + 14, packet->len - 14) < 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
return false;
}
break;
case DEVICE_TYPE_TAP:
- if(write(device_fd, packet->data, packet->len) < 0) {
+ 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;
}
/*
sptps.c -- Simple Peer-to-Peer Security
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2011-2014 Guus Sliepen <guus@tinc-vpn.org>,
2010 Brandon L. Black <blblack@gmail.com>
This program is free software; you can redistribute it and/or modify
}
// 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 char *data, uint16_t len) {
+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
}
}
// 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 char *data, uint16_t len) {
+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);
}
// Send an application record.
-bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
+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)
}
}
+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 error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture);
+
+ // Unless we have seen lots of them, in which case we consider the others lost.
+ 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 error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno);
+ } else if (update_state) {
+ // We missed some packets. Mark them in the bitmap as being late.
+ for(int 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 char *data, size_t len) {
+bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len) {
if(!s->instate || len < 21)
return error(s, EIO, "Received short packet");
- // TODO: just decrypt without updating the replay window
+ uint32_t seqno;
+ memcpy(&seqno, data, 4);
+ seqno = ntohl(seqno);
+ if (!sptps_check_seqno(s, seqno, false))
+ return false;
- return true;
+ char buffer[len];
+ size_t outlen;
+ return chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen);
}
// Receive incoming data, datagram version.
if(!chacha_poly1305_decrypt(s->incipher, seqno, data + 4, len - 4, buffer, &outlen))
return error(s, EIO, "Failed to decrypt and verify packet");
- // 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.
- if(s->farfuture++ < s->replaywin >> 2)
- return error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture);
-
- // Unless we have seen lots of them, in which case we consider the others lost.
- warning(s, "Lost %d packets\n", seqno - s->inseqno);
- // 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 error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno);
- } else {
- // We missed some packets. Mark them in the bitmap as being late.
- for(int i = s->inseqno; i < seqno; i++)
- s->late[(i / 8) % s->replaywin] |= 1 << i % 8;
- }
- }
-
- // Mark the current packet as not being late.
- s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8);
- s->farfuture = 0;
- }
-
- if(seqno >= s->inseqno)
- s->inseqno = seqno + 1;
-
- if(!s->inseqno)
- s->received = 0;
- else
- s->received++;
+ if(!sptps_check_seqno(s, seqno, true))
+ return false;
// Append a NULL byte for safety.
buffer[len - 20] = 0;
if(!s->instate)
return error(s, EIO, "Application record received before handshake finished");
if(!s->receive_record(s->handle, type, buffer + 1, len - 21))
- abort();
+ return false;
} else if(type == SPTPS_HANDSHAKE) {
if(!receive_handshake(s, buffer + 1, len - 21))
- abort();
+ return false;
} else {
return error(s, EIO, "Invalid record type %d", type);
}
}
// Receive incoming data. Check if it contains a complete record, if so, handle it.
-bool sptps_receive_data(sptps_t *s, const char *data, size_t len) {
+bool sptps_receive_data(sptps_t *s, const void *data, size_t len) {
if(!s->state)
return error(s, EIO, "Invalid session state zero");
}
// Start a SPTPS session.
-bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t *mykey, ecdsa_t *hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) {
+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);
/*
sptps.h -- Simple Peer-to-Peer Security
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2011-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
// Overhead for datagrams
#define SPTPS_DATAGRAM_OVERHEAD 21
-typedef bool (*send_data_t)(void *handle, uint8_t type, const char *data, size_t len);
-typedef bool (*receive_record_t)(void *handle, uint8_t type, const char *data, uint16_t len);
+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;
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 char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record);
+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 char *data, uint16_t len);
-extern bool sptps_receive_data(sptps_t *s, const char *data, size_t len);
+extern bool sptps_send_record(sptps_t *s, uint8_t type, const void *data, uint16_t len);
+extern bool 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 char *data, size_t len);
+extern bool sptps_verify_datagram(sptps_t *s, const void *data, size_t len);
#endif
static char *program_name;
+void logger(int level, int priority, const char *format, ...) {
+ 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"
/*
sptps_speed.c -- SPTPS benchmark
- Copyright (C) 2013 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2013-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
*/
#include "system.h"
+#include "utils.h"
#include <poll.h>
char *logfilename = NULL;
struct timeval now;
-static bool send_data(void *handle, uint8_t type, const char *data, size_t len) {
+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 char *data, uint16_t len) {
+static bool receive_record(void *handle, uint8_t type, const void *data, uint16_t len) {
return true;
}
int fd[2];
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) {
- fprintf(stderr, "Could not create a UNIX socket pair: %s\n", strerror(errno));
+ fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
return 1;
}
close(fd[1]);
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, fd)) {
- fprintf(stderr, "Could not create a UNIX socket pair: %s\n", strerror(errno));
+ fprintf(stderr, "Could not create a UNIX socket pair: %s\n", sockstrerror(sockerrno));
return 1;
}
/*
sptps_test.c -- Simple Peer-to-Peer Security test program
- Copyright (C) 2011-2013 Guus Sliepen <guus@tinc-vpn.org>,
+ Copyright (C) 2011-2014 Guus Sliepen <guus@tinc-vpn.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
static int in = 0;
static int out = 1;
-static bool send_data(void *handle, uint8_t type, const char *data, size_t len) {
+static bool send_data(void *handle, uint8_t type, const void *data, size_t len) {
char hex[len * 2 + 1];
bin2hex(data, hex, len);
if(verbose)
return true;
}
-static bool receive_record(void *handle, uint8_t type, const char *data, uint16_t len) {
+static bool receive_record(void *handle, uint8_t type, const void *data, uint16_t len) {
if(verbose)
fprintf(stderr, "Received type %d record of %hu bytes:\n", type, len);
if(!writeonly)
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", strerror(errno));
+ 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", strerror(errno));
+ fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno));
return 1;
}
if(initiator) {
if(connect(sock, ai->ai_addr, ai->ai_addrlen)) {
- fprintf(stderr, "Could not connect to peer: %s\n", strerror(errno));
+ 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", strerror(errno));
+ 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", strerror(errno));
+ 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", strerror(errno));
+ fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
return 1;
}
} else {
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", strerror(errno));
+ 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", strerror(errno));
+ fprintf(stderr, "Could not accept connection: %s\n", sockstrerror(sockerrno));
return 1;
}
}
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(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", strerror(errno));
+ fprintf(stderr, "Could not read from socket: %s\n", sockstrerror(sockerrno));
return 1;
}
if(len == 0) {
subnet->owner = n;
splay_insert(subnet_tree, subnet);
- splay_insert(n->subnet_tree, subnet);
+ if (n)
+ splay_insert(n->subnet_tree, subnet);
subnet_cache_flush();
}
void subnet_del(node_t *n, subnet_t *subnet) {
- splay_delete(n->subnet_tree, subnet);
+ if (n)
+ splay_delete(n->subnet_tree, subnet);
splay_delete(subnet_tree, subnet);
subnet_cache_flush();
if(!memcmp(address, &p->net.mac.address, sizeof *address)) {
r = p;
- if(p->owner->status.reachable)
+ if(!p->owner || p->owner->status.reachable)
break;
}
}
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;
}
}
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;
}
}
// Prepare environment variables to be passed to the script
char *envp[10] = {NULL};
- xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
- xasprintf(&envp[1], "DEVICE=%s", device ? : "");
- xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
- xasprintf(&envp[3], "NODE=%s", owner->name);
+ int n = 0;
+ xasprintf(&envp[n++], "NETNAME=%s", netname ? : "");
+ xasprintf(&envp[n++], "DEVICE=%s", device ? : "");
+ xasprintf(&envp[n++], "INTERFACE=%s", iface ? : "");
+ xasprintf(&envp[n++], "NODE=%s", owner->name);
if(owner != myself) {
sockaddr2str(&owner->address, &address, &port);
- // 4 and 5 are reserved for SUBNET and WEIGHT
- xasprintf(&envp[6], "REMOTEADDRESS=%s", address);
- xasprintf(&envp[7], "REMOTEPORT=%s", port);
+ xasprintf(&envp[n++], "REMOTEADDRESS=%s", address);
+ xasprintf(&envp[n++], "REMOTEPORT=%s", port);
free(port);
free(address);
}
- xasprintf(&envp[8], "NAME=%s", myself->name);
+ xasprintf(&envp[n++], "NAME=%s", myself->name);
name = up ? "subnet-up" : "subnet-down";
weight = empty;
// Prepare the SUBNET and WEIGHT variables
- if(envp[4])
- free(envp[4]);
- if(envp[5])
- free(envp[5]);
- xasprintf(&envp[4], "SUBNET=%s", netstr);
- xasprintf(&envp[5], "WEIGHT=%s", weight);
+ free(envp[n]);
+ free(envp[n + 1]);
+ xasprintf(&envp[n], "SUBNET=%s", netstr);
+ xasprintf(&envp[n + 1], "WEIGHT=%s", weight);
execute_script(name, envp);
}
weight = empty;
// Prepare the SUBNET and WEIGHT variables
- xasprintf(&envp[4], "SUBNET=%s", netstr);
- xasprintf(&envp[5], "WEIGHT=%s", weight);
+ xasprintf(&envp[n], "SUBNET=%s", netstr);
+ xasprintf(&envp[n + 1], "WEIGHT=%s", weight);
execute_script(name, envp);
}
send_request(c, "%d %d %s %s",
CONTROL, REQ_DUMP_SUBNETS,
- netstr, subnet->owner->name);
+ netstr, subnet->owner ? subnet->owner->name : "(broadcast)");
}
return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
#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) {
/* Ascii representation of subnets */
bool str2net(subnet_t *subnet, const char *subnetstr) {
- int i, l;
- uint16_t x[8];
- int weight = 10;
-
- if(sscanf(subnetstr, "%hu.%hu.%hu.%hu/%d#%d",
- &x[0], &x[1], &x[2], &x[3], &l, &weight) >= 5) {
- if(l < 0 || l > 32)
+ 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;
+ }
- subnet->type = SUBNET_IPV4;
- subnet->net.ipv4.prefixlength = l;
- subnet->weight = weight;
-
- for(int i = 0; i < 4; i++) {
- if(x[i] > 255)
- return false;
- subnet->net.ipv4.address.x[i] = x[i];
- }
+ 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;
- return true;
+ if (prefixlength < 0)
+ return false;
}
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d",
- &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
- &l, &weight) >= 9) {
- if(l < 0 || l > 128)
+ 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 agressively 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_IPV6;
- subnet->net.ipv6.prefixlength = l;
+ subnet->type = SUBNET_MAC;
subnet->weight = weight;
-
- for(i = 0; i < 8; i++)
- subnet->net.ipv6.address.x[i] = htons(x[i]);
-
+ for(int i = 0; i < 6; i++)
+ subnet->net.mac.address.x[i] = x[i];
return true;
}
- if(sscanf(subnetstr, "%hu.%hu.%hu.%hu#%d", &x[0], &x[1], &x[2], &x[3], &weight) >= 4) {
+ 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 = 32;
+ subnet->net.ipv4.prefixlength = prefixlength;
subnet->weight = weight;
-
- for(i = 0; i < 4; i++) {
- if(x[i] > 255)
+ for(int i = 0; i < 4; i++) {
+ if (x[i] > 255)
return false;
subnet->net.ipv4.address.x[i] = x[i];
}
-
return true;
}
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx#%d",
- &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &weight) >= 8) {
- subnet->type = SUBNET_IPV6;
- subnet->net.ipv6.prefixlength = 128;
- subnet->weight = weight;
-
- for(i = 0; i < 8; i++)
- subnet->net.ipv6.address.x[i] = htons(x[i]);
-
- return true;
- }
+ /* IPv6 */
- if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx#%d",
- &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &weight) >= 6) {
- subnet->type = SUBNET_MAC;
- subnet->weight = weight;
-
- for(i = 0; i < 6; i++)
- subnet->net.mac.address.x[i] = x[i];
-
- return true;
+ 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]);
}
- // IPv6 short form
- if(strstr(subnetstr, "::")) {
- const char *p;
- char *q;
- int colons = 0;
-
- // Count number of colons
- for(p = subnetstr; *p; p++)
- if(*p == ':')
- colons++;
-
- if(colons > 7)
+ 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;
- // Scan numbers before the double colon
- p = subnetstr;
- for(i = 0; i < colons; i++) {
- if(*p == ':')
- break;
- x[i] = strtoul(p, &q, 0x10);
- if(!q || p == q || *q != ':')
- return false;
- p = ++q;
- }
-
- p++;
- colons -= i;
- if(!i) {
- p++;
- colons--;
- }
-
- if(!*p || *p == '/' || *p == '#')
- colons--;
+ /* 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);
- // Fill in the blanks
- for(; i < 8 - colons; i++)
- x[i] = 0;
-
- // Scan the remaining numbers
- for(; i < 8; i++) {
- x[i] = strtoul(p, &q, 0x10);
- if(!q || p == q)
- return false;
- if(i == 7) {
- p = q;
- break;
- }
- if(*q != ':')
- return false;
- p = ++q;
- }
+ /* 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);
- l = 128;
- if(*p == '/')
- sscanf(p, "/%d#%d", &l, &weight);
- else if(*p == '#')
- sscanf(p, "#%d", &weight);
+ /* 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(l < 0 || l > 128)
+ 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 = l;
+ subnet->net.ipv6.prefixlength = prefixlength;
subnet->weight = weight;
-
- for(i = 0; i < 8; i++)
+ for(int i = 0; i < 8; i++)
subnet->net.ipv6.address.x[i] = htons(x[i]);
-
return true;
}
return false;
}
+ int result;
+ int prefixlength = -1;
switch (subnet->type) {
case SUBNET_MAC:
- snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx#%d",
+ 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],
- subnet->weight);
+ subnet->net.mac.address.x[5]);
+ netstr += result;
+ len -= result;
break;
case SUBNET_IPV4:
- snprintf(netstr, len, "%hu.%hu.%hu.%hu/%d#%d",
+ 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],
- subnet->net.ipv4.prefixlength,
- subnet->weight);
+ subnet->net.ipv4.address.x[3]);
+ netstr += result;
+ len -= result;
+ prefixlength = subnet->net.ipv4.prefixlength;
+ if (prefixlength == 32)
+ prefixlength = -1;
break;
- case SUBNET_IPV6:
- snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%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);
+ 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, "%hx:", 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);
+ netstr += result;
+ len -= result;
+ }
+
return true;
}
#include "control_common.h"
#include "crypto.h"
#include "ecdsagen.h"
+#include "fsck.h"
#include "info.h"
#include "invitation.h"
#include "names.h"
#include "utils.h"
#include "tincctl.h"
#include "top.h"
+#include "version.h"
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
static int code;
static int req;
static int result;
-static bool force = false;
+bool force = false;
bool tty = true;
bool confbasegiven = false;
bool netnamegiven = false;
static char *prompt;
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},
static void version(void) {
printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
- VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR);
- printf("Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others.\n"
+ VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR);
+ printf("Copyright (C) 1998-2014 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"
} 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"
" 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 keypair.\n"
+#else
" generate-keys [bits] Generate new RSA and Ed25519 public/private keypairs.\n"
" generate-rsa-keys [bits] Generate a new RSA public/private keypair.\n"
+#endif
" generate-ed25519-keys Generate a new Ed25519 public/private keypair.\n"
" dump Dump a list of one of the following things:\n"
" [reachable] nodes - all known nodes in the VPN\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 [--force] Import host configuration file(s) from standard input\n"
- " exchange [--force] Same as export followed by import\n"
- " exchange-all [--force] Same as export-all followed by import\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 INVITIATION\n"
" network [NETNAME] List all known networks, or switch to the one named NETNAME.\n"
+ " fsck Check the configuration files for problems.\n"
"\n");
printf("Report bugs to tinc@tinc-vpn.org.\n");
}
case 0: /* long option */
break;
+ case 'b':
+ tty = false;
+ break;
+
case 'c': /* config file */
confbase = xstrdup(optarg);
confbasegiven = true;
return true;
}
+#ifndef DISABLE_LEGACY
/*
Generate a public/private RSA keypair, and ask for a file to store
them in.
return true;
}
+#endif
char buffer[4096];
size_t blen = 0;
while(!(newline = memchr(buffer, '\n', blen))) {
int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
- if(result == -1 && errno == EINTR)
+ if(result == -1 && sockerrno == EINTR)
continue;
else if(result <= 0)
return false;
while(blen < len) {
int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
- if(result == -1 && errno == EINTR)
+ if(result == -1 && sockerrno == EINTR)
continue;
else if(result <= 0)
return false;
while(blen) {
int result = send(fd, p, blen, MSG_NOSIGNAL);
- if(result == -1 && errno == EINTR)
+ if(result == -1 && sockerrno == EINTR)
continue;
else if(result <= 0)
return false;
if(getaddrinfo(host, port, &hints, &res) || !res) {
if(verbose)
- fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno));
+ fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, sockstrerror(sockerrno));
return false;
}
#ifdef SO_NOSIGPIPE
static const int one = 1;
- setsockopt(c, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof one);
+ setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof one);
#endif
char data[4096];
int nargc = 0;
char **nargv = xzalloc((optind + argc) * sizeof *nargv);
- nargv[nargc++] = c;
+ 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
- execvp(c, nargv);
- fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno));
- return 1;
+ 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
pid_t pid = fork();
if(pid == -1) {
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;
switch(req) {
case REQ_DUMP_NODES: {
- int n = sscanf(line, "%*d %*d %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", node, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
- if(n != 16) {
+ int n = sscanf(line, "%*d %*d %s %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", node, id, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change);
+ if(n != 17) {
fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line);
return 1;
}
} else {
if(only_reachable && !status.reachable)
continue;
- printf("%s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %hd (min %hd max %hd)\n",
- node, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu);
+ 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 %hd (min %hd max %hd)\n",
+ node, id, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu);
}
} break;
case REQ_DUMP_EDGES: {
- int n = sscanf(line, "%*d %*d %s %s %s port %s %x %d", from, to, host, port, &options, &weight);
- if(n != 6) {
+ int n = sscanf(line, "%*d %*d %s %s %s port %s %s port %s %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;
}
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 options %x weight %d\n", from, to, host, port, options, weight);
+ 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;
continue;
if(*value) {
fclose(f);
- return strdup(value);
+ return replace_name(value);
}
}
{"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},
{"Device", VAR_SERVER},
+ {"DeviceStandby", VAR_SERVER},
{"DeviceType", VAR_SERVER},
{"DirectOnly", VAR_SERVER},
{"Ed25519PrivateKeyFile", VAR_SERVER},
{"ScriptsInterpreter", VAR_SERVER},
{"StrictSubnets", VAR_SERVER},
{"TunnelServer", VAR_SERVER},
+ {"UDPDiscovery", VAR_SERVER},
+ {"UDPDiscoveryKeepaliveInterval", VAR_SERVER},
+ {"UDPDiscoveryInterval", VAR_SERVER},
+ {"UDPDiscoveryTimeout", VAR_SERVER},
{"UDPRcvBuf", VAR_SERVER},
{"UDPSndBuf", VAR_SERVER},
{"VDEGroup", VAR_SERVER},
}
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;
}
}
}
// Add new variable if necessary.
- if(action > 0 || (action == 0 && !set)) {
+ 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)
+ if(found) {
+ return 0;
+ } else {
fprintf(stderr, "No matching configuration variables found.\n");
- return 1;
+ return 1;
+ }
}
// Make sure we wrote everything...
return 0;
}
-bool check_id(const char *name) {
- if(!name || !*name)
- return false;
-
- for(int i = 0; i < strlen(name); i++) {
- if(!isalnum(name[i]) && name[i] != '_')
- return false;
- }
-
- return true;
-}
-
static bool try_bind(int port) {
struct addrinfo *ai = NULL;
struct addrinfo hint = {
fprintf(f, "Name = %s\n", name);
fclose(f);
- if(!rsa_keygen(2048, false) || !ed25519_keygen(false))
+#ifndef DISABLE_LEGACY
+ if(!rsa_keygen(2048, false))
+ return 1;
+#endif
+
+ if(!ed25519_keygen(false))
return 1;
check_port(name);
}
static int cmd_generate_keys(int argc, char *argv[]) {
+#ifdef DISABLE_LEGACY
+ if(argc > 1) {
+#else
if(argc > 2) {
+#endif
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) && ed25519_keygen(true));
+#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 !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true);
}
+#endif
static int cmd_generate_ed25519_keys(int argc, char *argv[]) {
if(argc > 1) {
free(netname);
netname = strcmp(name, ".") ? xstrdup(name) : NULL;
- make_names();
xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase);
xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase);
xasprintf(&prompt, "%s> ", identname);
return 0;
}
+static int cmd_fsck(int argc, char *argv[]) {
+ if(argc > 1) {
+ fprintf(stderr, "Too many arguments!\n");
+ return 1;
+ }
+
+ return fsck(orig_argv[0]);
+}
+
static const struct {
const char *command;
int (*function)(int argc, char *argv[]);
{"set", cmd_config},
{"init", cmd_init},
{"generate-keys", cmd_generate_keys},
+#ifndef DISABLE_LEGACY
{"generate-rsa-keys", cmd_generate_rsa_keys},
+#endif
{"generate-ed25519-keys", cmd_generate_ed25519_keys},
{"help", cmd_help},
{"version", cmd_version},
{"invite", cmd_invite},
{"join", cmd_join},
{"network", cmd_network},
+ {"fsck", cmd_fsck},
{NULL, NULL},
};
program_name = argv[0];
orig_argv = argv;
orig_argc = argc;
+ tty = isatty(0) && isatty(1);
if(!parse_options(argc, argv))
return 1;
srand(time(NULL));
crypto_init();
- tty = isatty(0) && isatty(1);
-
if(optind >= argc)
return cmd_shell(argc, argv);
#define __TINC_TINCCTL_H__
extern bool tty;
+extern bool force;
extern char line[4096];
extern int fd;
extern char buffer[4096];
#include "control.h"
#include "crypto.h"
#include "device.h"
+#include "event.h"
#include "logger.h"
#include "names.h"
#include "net.h"
#include "protocol.h"
#include "utils.h"
#include "xalloc.h"
+#include "version.h"
/* If nonzero, display usage information and exit. */
static bool show_help = false;
#ifdef HAVE_MINGW
static struct WSAData wsa_state;
-CRITICAL_SECTION mutex;
int main2(int argc, char **argv);
#endif
#ifdef HAVE_MINGW
# define setpriority(level) !SetPriorityClass(GetCurrentProcess(), (level))
+
+static void stop_handler(void *data, int flags) {
+ event_exit();
+}
+
+static BOOL WINAPI console_ctrl_handler(DWORD 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
if(show_version) {
printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
- VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR);
+ VERSION, BUILD_DATE, BUILD_TIME, PROT_MAJOR, PROT_MINOR);
printf("Copyright (C) 1998-2014 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"
#endif
#ifdef HAVE_MINGW
- if(!do_detach || !init_service())
- return main2(argc, argv);
- else
- return 1;
+ io_add_event(&stop_io, stop_handler, NULL, WSACreateEvent());
+ if (stop_io.event == FALSE)
+ abort();
+
+ int result;
+ if(!do_detach || !init_service()) {
+ SetConsoleCtrlHandler(console_ctrl_handler, TRUE);
+ result = main2(argc, argv);
+ } else
+ 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);
#endif
char *priority = NULL;
#ifdef HAVE_CURSES
+#undef KEY_EVENT /* There are conflicting declarations for KEY_EVENT in Windows wincon.h and curses.h. */
#include <curses.h>
#include "control_common.h"
}
case 2: {
- if((inlen = read(data_fd, packet->data, MTU)) <= 0) {
+ if((inlen = read(data_fd, DATA(packet), MTU)) <= 0) {
logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
event_exit();
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(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
event_exit();
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+/";
memcpy(&value, bitfield, size);
return value;
}
+
+bool check_id(const char *id) {
+ if(!id || !*id)
+ return false;
+
+ for(; *id; id++)
+ if(!isalnum(*id) && *id != '_')
+ 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;
+}
#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 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);
+extern bool check_id(const char *);
+char *replace_name(const char *name);
+
#endif /* __TINC_UTILS_H__ */
}
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(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno));
event_exit();
}
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(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
event_exit();
--- /dev/null
+/*
+ version.c -- version information
+ Copyright (C) 2014 Etienne Dechamps <etienne@edechamps.fr>
+
+ 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"
+
+/* 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__;
--- /dev/null
+/*
+ version.h -- header for version.c
+ Copyright (C) 2014 Etienne Dechamps <etienne@edechamps.fr>
+
+ 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_VERSION_H__
+#define __TINC_VERSION_H__
+
+extern const char* const BUILD_DATE;
+extern const char* const BUILD_TIME;
+
+#endif /* __TINC_VERSION_H__ */
# Test tinc command line options that should not work
+$tinc $c1 -n foo get somethingreallyunknown && exit 1 || true
$tinc $c1 --net && exit 1 || true
$tinc $c1 --net get name && exit 1 || true
$tinc $c1 foo && exit 1 || true
$tinc $c1 add Subnet 3
test "`$tinc $c1 get Subnet`" = "1
2
-2
3"
$tinc $c1 del Subnet 2
test "`$tinc $c1 get Subnet`" = "1
$tinc $c1 add bar.Subnet 3
test "`$tinc $c1 get bar.Subnet`" = "1
2
-2
3"
$tinc $c1 del bar.Subnet 2
test "`$tinc $c1 get bar.Subnet`" = "1