Normally all requests sent via the meta connections are checked so that they
cannot be larger than the input buffer. However, when packets are forwarded via
meta connections, they are copied into a packet buffer without checking whether
it fits into it. Since the packet buffer is allocated on the stack, this in
effect allows an authenticated remote node to cause a stack overflow.
This issue was found by Martin Schobert.
We were checking only for readability, which is not a problem for normal
connections, since the server side of a connection will always send an ID
request. But when using a proxy, the proxy server doesn't send anything before
the client, so tinc would not see that its connection to the proxy had already
been established.
Tinc never restarts PMTU discovery unless a node becomes unreachable. However,
it can be that the PMTU was very low during the initial discovery, but has
increased later. To detect this, tinc now tries to send an extra packet every
PingInterval, with a size slightly higher than the currently known PMTU. If
this packet is succesfully received back, we partially restart PMTU discovery
to find out the new maximum.
Conflicts:
src/net_packet.c
Commit dd07c9fc1f broke the reception of datagram
SPTPS packets, by undoing the conversion of the sequence number to host byte
order before comparison. This caused error messages like "Packet is 16777215
seqs in the future, dropped (1)".
Tinc uses Kruskal's algorithm to calculate a MST. However, this was broken in
commit 6e80da3370. Revert back to the working
algorithm from tinc 1.0.
Thanks to Cheng LI for spotting the problem.
Without adding any extra traffic, we can measure round trip times, estimate the
bandwidth and packet loss between nodes. The RTT and bandwidth can be measured
by timing the MTU probe packets. The RTT is the difference between the time a
burst of MTU probes was sent and when the first reply is received. The
bandwidth can be estimated by multiplying the size of the probe packets by the
time between succesive received probe replies of the same burst. The packet
loss can be estimated for incoming traffic by comparing how many packets have
actually been received to the increase in the sequence numbers.
The estimates are not perfect. Especially bandwidth is difficult to measure,
the only accurate way is to continuously send as much data as possible, but
that is obviously not desirable. The packet loss rate is also almost always
a few percent when sending a lot of data over the VPN via TCP, since TCP
*needs* packet loss to work properly.
Keep track of the number of correct, non-replayed UDP packets that have been
received, regardless of their content. This can be compared to the sequence
number to determine the real packet loss.
There are several reasons for this:
- MacOS/X doesn't support polling the tap device using kqueue, requiring a
workaround to fall back to select().
- On Windows only sockets are properly handled, therefore tinc uses a second
thread that does a blocking ReadFile() on the TAP-Win32/64 device. However,
this does not mix well with libevent.
- Libevent, event just the core, is quite large, and although it is easy to get
and install on many platforms, it can be a burden.
- Libev is more lightweight and seems technically superior, but it doesn't
abstract away all the platform differences (for example, async events are not
supported on Windows).
We don't need to search the whole edge tree, we can use the node's own edge
tree since each edge has a pointer to its reverse. Also, we do need to make
sure we try the reflexive address often.
Before it would always use the first socket, and always send an IPv4 broadcast packet. That
works fine in a lot of situations, but it is better to try all sockets, and to send IPv6 packets
on IPv6 sockets. This is especially important for users that are on IPv6-only networks or that
have multiple physical network interfaces, although in the latter case it probably requires
them to use the ListenAddress variable to create a separate socket for each interface.
Before, when tinc saw a packet larger than the PMTU with a VLAN tag, it would
not know what to do with it, and would just forward it via TCP. Now, tinc
handles 802.1q packets correctly, as long as there is only one tag.
When set to a non-zero value, tinc will try to maintain exactly that number of
meta connections to other nodes. If there are not enough connections, it will
periodically try to set up an outgoing connection to a random node. If there
are too many connections, it will periodically try to remove an outgoing
connection.
Only the very first packet of an SPTPS session should be send with REQ_KEY,
this signals the peer to abort any previous session and start a new one as
well.
Most of the code doesn't care whether requests are terminated with a newline or
not, except that when requests are forwarded, it is assumed they do not have
one and a newline is added. When a node using SPTPS receives a request from
another SPTPS-using node, and forwards it to a non-SPTPS-using node, this will
result in two consecutive newlines, which the latter node will see as an empty,
and thus invalid, request.
The tree functions were never used on the connection_tree, a list is more appropriate.
Also be more paranoid about connections disappearing while traversing the list.
Struct outgoing_ts and connection_ts were depending too much on each other,
causing lots of problems, especially the reuse of a connection_t. Now, whenever
a connection is closed it is immediately removed from the list of connections
and destroyed.
Similar to old style key exchange requests, keep track of whether a key
exchange is already in progress and how long it took. If no key is known yet
or if key exchange takes too long, (re)start a new key exchange.
Most fields should be zero when reusing a connection. In particular, when an
outgoing connection to a node which is reachable on more than one address is
made, the second connection to that node will have status.encryptout set but
outctx will be NULL, causing a NULL pointer dereference when
EVP_EncryptUpdate() is called in send_meta() when it shouldn't.
When starting tincd, tincctl now strips non-options from the command line, and
sets argv[0] to the name of the tincd command instead of copying its own
command name.
When stopping a running tincd, tincctl now waits for it to terminate.
Internally, tinc maintains a directed graph of the meta connections between
nodes. However, this causes graphviz to draw two lines between nodes, which is
not always desirable. The "dump graph" command now defaults to dumping an
undirected graph, the "dump digraph" command will dump a directed graph.
This new setting allows choosing a custom script interpreter used for the various tinc callbacks.
If none is specified, the script itself is called as executable (as before).
This is particularly useful when storing tinc configuration and script on a mount point with no-exec attribute.
Commented non-existing functions in android NDK.
Prefix scripts execution with shell binary to allow execution on no-exec mount points.
Everyything is currently hard coded, while it should use pre-compiler variables...
When two nodes which support SPTPS want to send packets to each other, they now
always use SPTPS. The node initiating the SPTPS session send the first SPTPS
packet via an extended REQ_KEY messages. All other handshake messages are sent
using ANS_KEY messages. This ensures that intermediate nodes using an older
version of tinc can still help with NAT traversal. After the authentication
phase is over, SPTPS packets are sent via UDP, or are encapsulated in extended
REQ_KEY messages instead of PACKET messages.
Otherwise, the call to daemon() could close filedescriptors in use by libevent
itself; for example if it uses kqueue or epoll instead of a select() or poll()
backend.
In retry() the function do_outgoing_connection() is called, which can delete
items from the connection_tree, so when walking the tree we must first save the
pointer to the next item.
The used remote protocol can change between two reconnects, aka if
the remote side has enabled/disabled for example their ExperimentalProtocols
setting.
Proxy type "exec" can be used to have an external script or binary set
up an outgoing connection. Standard input and output will be used to
exchange data with the external command. The variables REMOTEADDRESS and
REMOTEPORT are set to the intended destination address and port.
When the Proxy option is used, outgoing connections will be made via the
specified proxy. There is no support for authentication methods or for having
the proxy forward incoming connections, and there is no attempt to proxy UDP.
When the "Broadcast = direct" option is used, broadcast packets are not sent
and forwarded via the Minimum Spanning Tree to all nodes, but are sent directly
to all nodes that can be reached in one hop.
One use for this is to allow running ad-hoc routing protocols, such as OLSR, on
top of tinc.
Most likeley the error is that there just is no valid key inside the used
host file, and in this case errno just contains a random value from the
last previously failed call.
When the Name starts with a $, the rest will be interpreted as the name of an
environment variable containing the real Name. When Name is $HOST, but this
environment variable does not exist, gethostname() will be used to set the
Name. In both cases, illegal characters will be converted to underscores.
If the LISTEN_FDS environment variable is set and tinc is run in the
foreground, tinc will use filedescriptors 3 to 3 + LISTEN_FDS for its listening
TCP sockets. For now, tinc will create matching listening UDP sockets itself.
There is no dependency on systemd or on libsystemd-daemon.
DeviceType = multicast allows one to specify a multicast address and port with
a Device statement. Tinc will then read/send packets to that multicast group
instead of to a tun/tap device. This allows interaction with UML, QEMU and KVM
instances that are listening on the same group.
When making outgoing connections, tinc goes through the list of Addresses and
tries all of them until one succeeds. However, before it would consider
establishing a TCP connection a success, even when the authentication failed.
This would be a problem if the first Address would point to a hostname and port
combination that belongs to the wrong tinc node, or perhaps even to a non-tinc
service, causing tinc to endlessly try this Address instead of moving to the
next one.
Problem found by Delf Eldkraft.
* Everything is identical except the headers of the records.
* Instead of sending explicit message length and having an implicit sequence
number, datagram mode has an implicit message length and an explicit sequence
number.
* The sequence number is used to set the most significant bytes of the counter.
Seeking in files and rewriting parts of them does not seem to work properly on
Windows. Instead, when old RSA keys are found when generating new ones, the
file containing the old keys is copied to a temporary file where the changes
are made, and that file is renamed back to the original filename. On Windows,
we cannot atomically replace files with a rename(), so we need to move the
original file out of the way first. If anything fails, the new code will warn
that the user has to solve the problem by hand.
This allows tincctl to receive log messages from a running tincd,
independent of what is logged to syslog or to file. Tincctl can receive
debug messages with an arbitrary level.
This allows administrators who frequently want to work with one tinc
network to omit the -n option. Since the NETNAME variable is set by
tincd when executing scripts, this makes it slightly easier to use
tincctl from within scripts.
The Broadcast option can be used to cause tinc to drop all broadcast and
multicast packets. This option might be expanded in the future to selectively
allow only some broadcast packet types.
The code introduced in commit 41a05f59ba is not
needed anymore, since tinc has been able to handle UDP packets from a different
source address than those of the TCP packets since 1.0.10. When using multiple
BindToAddress statements, this code does not make sense anymore, we do want the
kernel to choose the source address on its own.
Tinc will now, by default, decrement the TTL field of incoming IPv4 and IPv6
packets, before forwarding them to the virtual network device or to another
node. Packets with a TTL value of zero will be dropped, and an ICMP Time
Exceeded message will be sent back.
This behaviour can be disabled using the DecrementTTL option.
Scripts called by tinc would inherit its open filedescriptors. This could
be a problem if other long-running daemons are started from those scripts,
if those daemons would not close all filedescriptors before going into the
background.
Problem found and solution suggested by Nick Hibma.
Apart from the platform specific tun/tap driver, link with the dummy and
raw_socket devices, and optionally with support for UML and VDE devices.
At runtime, the DeviceType option can be used to select which driver to
use.
* Exchange nonce and ECDH public key first, calculate the ECDSA signature
over the complete key exchange.
* Make an explicit distinction between client and server in the signatures.
* Add more comments and replace some magic numbers by #defines.
Thanks to Erik Tews for very helpful hints and comments!
In case the config file could not be opened a new but unitialized RSA structure
would be returned, causing a segmentation fault later on. This would only
happen in the case that the config file could be opened before, but not when
read_rsa_public_key() was called. This situation could occur when the --user
option was used, and the config files were not readable by the specified user.
Probably due to a merge, the try_harder() function had duplicated the
rate-limiting code for detecting the sender node based on the HMAC of the
packet. This prevented this detection from running at all. The function is now
identical again to that in the 1.0 branch.
sometimes argv[0] will have directory-less name (when the
command is started by shell searching in $PATH for example).
For tincctl start we want the same rules to run tincd as for
tincctl itself (having full path is better but if shell does
not provide one we've no other choice). Previous code tried
to run ./tincd in this case, which is obviously wrong.
This is a fix for the previous commit.
Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
For tincctl start, run tincd from dirname($0) not SBINDIR -
this allows painless alternative directory installation and
running from build directory too.
Also while at it, pass the rest of command line to tincd, not
only options before "start" argument. This way it's possible
to pass options to tincd like this:
tincctl -n net start -- -d 1 -R -U tincuser ...
And also add missing newline at the end of error message there.
Signed-Off-By: Michael Tokarev <mjt@tls.msk.ru>
Encryption and authentication of the meta connection is spread out over
meta.c and protocol_auth.c. The new protocol was added there as well,
leading to spaghetti code. To improve things, the new protocol will now
be implemented in sptps.[ch].
The goal is to have a very simplified version of TLS. There is a record
layer, and there are only two record types: application data and
handshake messages. The handshake message contains a random nonce, an
ephemeral ECDH public key, and an ECDSA signature over the former. After
the ECDH public keys are exchanged, a shared secret is calculated, and a
TLS style PRF is used to generate the key material for the cipher and
HMAC algorithm, and further communication is encrypted and authenticated.
A lot of the simplicity comes from the fact that both sides must have
each other's public keys in advance, and there are no options to choose.
There will be one fixed cipher suite, and both peers always authenticate
each other. (Inspiration taken from Ian Grigg's hypotheses[0].)
There might be some compromise in the future, to enable or disable
encryption, authentication and compression, but there will be no choice
of algorithms. This will allow SPTPS to be built with a few embedded
crypto algorithms instead of linking with huge crypto libraries.
The API is also kept simple. There is a start and a stop function. All
data necessary to make the connection work is passed in the start
function. Instead having both send- and receive-record functions, there
is a send-record function and a receive-data function. The latter will
pass protocol data received from the peer to the SPTPS implementation,
which will in turn call a receive-record callback function when
necessary. This hides all the handshaking from the application, and is
completely independent from any event loop or socket characteristics.
[0] http://iang.org/ssl/hn_hypotheses_in_secure_protocol_design.html
This is mainly important for Windows, where the select() call in the
main thread is not being woken up when the tapreader thread calls
route(), causing a delay of up to 1 second before the output buffer is
flushed. This would cause bad performance when UDP communication is not
possible.
First of all, if there really are two nodes with the same name, much
more than 10 contradicting ADD_EDGE and DEL_EDGE messages will be sent.
Also, we forgot to reset the counters when nothing happened.
In case there is a ADD_EDGE/DEL_EDGE storm, we do not shut down, but
sleep an increasing amount of time, allowing tinc to recover gracefully
from temporary failures.
The length parameter for the encoding functions is the length of the
binary input, and for the decoding functions it is the maximum size of
the binary output.
The return value is always the length of the resulting output, excluding
the terminating NULL character for the encoding routines.
All functions can encode and decode in-place. The encoding functions
will always write a terminating NULL character, and the decoding
functions will stop at a NULL character.
If we don't have ECDSA keys for the node we connect to, set protocol_minor
to 1, to indicate this to the other end. This will first complete the
old way of authentication with RSA keys, and will then exchange ECDSA keys.
The connection will be terminated right afterwards, and the next attempt
will use ECDSA keys.
The generate-keys command now generates both an RSA and an ECDSA keypair,
but one can generate-rsa-keys or generate-ecdsa-keys to just generate one type.
It is modelled after the pseudorandom function from RFC4346 (TLS 1.1), the only
significant change is the use of SHA512 and Whirlpool instead of MD5 and SHA1.
REQ_KEY requests have an extra field indicating key exchange version.
If it is present and > 0, the sender supports ECDH. If the receiver also
does, then it will generate a new keypair and sends the public key in a
ANS_KEY request with "ECDH:" prefixed. The ans_key_h() function will
compute the shared secret, which, at the moment,is used as is to set the
cipher and HMAC keys. However, this must be changed to use a proper KDF.
In the future, the ECDH key exchange must also be signed.
The pid is now written first, so that a version 1.0.x tincd can be used to stop
a running version 1.1 tincd. Getsockname() is used to determine the address of
the first listening socket, so that tincctl can connect to the local tincd even
if AddressFamily = ipv6, or if BindToAddress or BindToInterface is used.
Instead of UNIX time, the log messages now start with the time in RFC3339
format, which human-readable and still easy for the computer to parse and sort.
The HUP signal will also cause the log file to be closed and reopened, which is
useful when log rotation is used. If there is an error while opening the log
file, this is logged to stderr.
But we do ignore SIGPIPE, and tinc 1.0.x signals that are no longer used
(SIGUSR1 and SIGUSR2), since the default handler of these signals is to
terminate tincd immediately.
Although we use qsort(), which is not guaranteed to be stable, resorting the
previously sorted array is more stable than recreating and resorting the array
each time.
We live in the 21st century, and we require C99 semantics, so we do not need to
work around buggy libcs. The xmalloc() and related functions are now static
inline functions.
We don't override any signal handlers anymore except those for SIGPIPE and
SIGCHLD. Fatal signals (SIGSEGV, SIGBUS etc.) will terminate tincd and
optionally dump core. The previous behaviour was to terminate gracefully and
try to restart, but that usually failed and made any core dump useless.
This would allow tincctl to connect to a remote tincd, or to a local tincd that
isn't listening on localhost, for example if it is using the BindToInterface or
BindToAddress options.
Also log an error when the input buffer contains more than MAXBUFSIZE bytes
already, instead of silently claiming the other side closed the connection.
Libevent 2.0's buffer code is not completely backward compatible with 1.4's.
In order to not (mis)use it anymore, we implement it ourselves. The buffers
are automatically expanding when necessary. When consuming data from the
buffer, no memmove()s are performed. Only when adding to the buffer would
write past the end do we shift everything back to the start.