#include "control_common.h"
#include "list.h"
#include "logger.h"
+#include "net.h"
#include "rsa.h"
#include "subnet.h"
#include "utils.h"
ecdsa_free(c->ecdsa);
free(c->hischallenge);
+ free(c->mychallenge);
buffer_clear(&c->inbuf);
buffer_clear(&c->outbuf);
io_del(&c->io);
if(c->socket > 0) {
- closesocket(c->socket);
+ if(c->status.tarpit) {
+ tarpit(c->socket);
+ } else {
+ closesocket(c->socket);
+ }
}
free(c->name);
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: 18;
+ unsigned int tarpit: 1; /* 1 if the connection should be added to the tarpit */
+ unsigned int unused: 17;
} connection_status_t;
#include "ecdsa.h"
int outcompression;
char *hischallenge; /* The challenge we sent to him */
+ char *mychallenge; /* The challenge we received */
struct buffer_t inbuf;
struct buffer_t outbuf;
}
}
+/* Put a misbehaving connection in the tarpit */
+void tarpit(int fd) {
+ static int pits[10] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+ static int next_pit = 0;
+
+ if(pits[next_pit] != -1) {
+ closesocket(pits[next_pit]);
+ }
+
+ pits[next_pit++] = fd;
+
+ if(next_pit >= sizeof pits / sizeof pits[0]) {
+ next_pit = 0;
+ }
+}
+
/*
Terminate a connection:
- Mark it as inactive
logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname);
} else {
logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname);
+ c->status.tarpit = true;
}
terminate_connection(c, c->edge);
void handle_meta_connection_data(connection_t *c) {
if(!receive_meta(c)) {
+ if(!c->status.control) {
+ c->status.tarpit = true;
+ }
+
terminate_connection(c, c->edge);
return;
}
extern int reload_configuration(void);
extern void load_all_nodes(void);
extern void try_tx(struct node_t *n, bool mtu);
+extern void tarpit(int fd);
#ifndef HAVE_MINGW
#define closesocket(s) close(s)
inpkt = outpkt;
- if (origlen > MTU / 64 + 20)
+ if(origlen > MTU / 64 + 20) {
origlen -= MTU / 64 + 20;
- else
+ } else {
origlen = 0;
+ }
}
if(inpkt->len > n->maxrecentlen) {
keylifetime = 3600;
}
- if (!get_config_bool(lookup_config(config_tree, "AutoConnect"), &autoconnect)) {
+ if(!get_config_bool(lookup_config(config_tree, "AutoConnect"), &autoconnect)) {
autoconnect = true;
}
int seconds_till_retry = 5;
int udp_rcvbuf = 1024 * 1024;
int udp_sndbuf = 1024 * 1024;
-int max_connection_burst = 100;
+int max_connection_burst = 10;
int fwmark;
listen_socket_t listen_socket[MAXSOCKETS];
// Check if we get many connections from the same host
static sockaddr_t prev_sa;
- static int tarpit = -1;
-
- if(tarpit >= 0) {
- closesocket(tarpit);
- tarpit = -1;
- }
if(!sockaddrcmp_noport(&sa, &prev_sa)) {
static int samehost_burst;
samehost_burst++;
if(samehost_burst > max_connection_burst) {
- tarpit = fd;
+ tarpit(fd);
return;
}
}
if(connection_burst >= max_connection_burst) {
connection_burst = max_connection_burst;
- tarpit = fd;
+ tarpit(fd);
return;
}
connection_add(c);
c->allow_request = ID;
- send_id(c);
}
#ifndef HAVE_MINGW
connection_add(c);
c->allow_request = ID;
-
- send_id(c);
}
#endif
return false;
}
- logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[atoi(request)], c->name, c->hostname, request);
+ int id = atoi(request);
+ logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[id], c->name, c->hostname, request);
request[len++] = '\n';
broadcast_meta(NULL, request, len);
return true;
} else {
- return send_meta(c, request, len);
+ if(id) {
+ return send_meta(c, request, len);
+ } else {
+ send_meta_raw(c, request, len);
+ return true;
+ }
}
}
buf[len] = 0;
- if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name)) {
+ if(!*buf || !*name || strcasecmp(buf, "Name") || !check_id(name) || !strcmp(name, myself->name)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Invalid invitation file %s\n", cookie);
fclose(f);
return false;
free(c->name);
c->name = xstrdup("<control>");
+ if(!c->outgoing) {
+ send_id(c);
+ }
+
return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid());
}
return false;
}
+ if(!c->outgoing) {
+ send_id(c);
+ }
+
if(!send_request(c, "%d %s", ACK, mykey)) {
return false;
}
/* Check if identity is a valid name */
- if(!check_id(name)) {
+ if(!check_id(name) || !strcmp(name, myself->name)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name,
c->hostname, "invalid name");
return false;
}
c->allow_request = ACK;
+
+ if(!c->outgoing) {
+ send_id(c);
+ }
+
return send_ack(c);
}
c->allow_request = METAKEY;
+ if(!c->outgoing) {
+ send_id(c);
+ }
+
if(c->protocol_minor >= 2) {
c->allow_request = ACK;
char label[25 + strlen(myself->name) + strlen(c->name)];
return false;
}
} else {
- c->incipher = NULL;
+ logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null cipher");
+ return false;
}
c->inbudget = cipher_budget(c->incipher);
return false;
}
} else {
- c->indigest = NULL;
+ logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "null digest");
+ return false;
}
c->status.decryptin = true;
const size_t len = rsa_size(c->rsa);
char buffer[len * 2 + 1];
- if(!c->hischallenge) {
- c->hischallenge = xrealloc(c->hischallenge, len);
- }
+ c->hischallenge = xrealloc(c->hischallenge, len);
/* Copy random data to the buffer */
char buffer[MAX_STRING_SIZE];
const size_t len = rsa_size(myself->connection->rsa);
- size_t digestlen = digest_length(c->indigest);
- char digest[digestlen];
if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname);
return false;
}
- /* Convert the challenge from hexadecimal back to binary */
-
- int inlen = hex2bin(buffer, buffer, sizeof(buffer));
-
/* Check if the length of the challenge is all right */
- if(inlen != len) {
+ if(strlen(buffer) != (size_t)len * 2) {
logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge length");
return false;
}
+ c->mychallenge = xrealloc(c->mychallenge, len);
+
+ /* Convert the challenge from hexadecimal back to binary */
+
+ hex2bin(buffer, c->mychallenge, len);
+
+ /* The rest is done by send_chal_reply() */
+
+ c->allow_request = CHAL_REPLY;
+
+ if(c->outgoing) {
+ return send_chal_reply(c);
+ } else {
+ return true;
+ }
+
+#endif
+}
+
+bool send_chal_reply(connection_t *c) {
+ const size_t len = rsa_size(myself->connection->rsa);
+ size_t digestlen = digest_length(c->indigest);
+ char digest[digestlen * 2 + 1];
+
/* Calculate the hash from the challenge we received */
- if(!digest_create(c->indigest, buffer, len, digest)) {
+ if(!digest_create(c->indigest, c->mychallenge, len, digest)) {
return false;
}
+ free(c->mychallenge);
+ c->mychallenge = NULL;
+
/* Convert the hash to a hexadecimal formatted string */
- bin2hex(digest, buffer, digestlen);
+ bin2hex(digest, digest, digestlen);
/* Send the reply */
- c->allow_request = CHAL_REPLY;
-
- return send_request(c, "%d %s", CHAL_REPLY, buffer);
-#endif
+ return send_request(c, "%d %s", CHAL_REPLY, digest);
}
bool chal_reply_h(connection_t *c, const char *request) {
c->hischallenge = NULL;
c->allow_request = ACK;
+ if(!c->outgoing) {
+ send_chal_reply(c);
+ }
+
return send_ack(c);
#endif
}
/* Check if names are valid */
- if(!check_id(from_name) || !check_id(to_name)) {
+ if(!check_id(from_name) || !check_id(to_name) || !strcmp(from_name, to_name)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name,
c->hostname, "invalid name");
return false;
/* Check if names are valid */
- if(!check_id(from_name) || !check_id(to_name)) {
+ if(!check_id(from_name) || !check_id(to_name) || !strcmp(from_name, to_name)) {
logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name,
c->hostname, "invalid name");
return false;
memcpy(s->hiskex, data, len);
- return send_sig(s);
+ if(s->initiator) {
+ return send_sig(s);
+ } else {
+ return true;
+ }
}
// Receive a SIGnature record, verify it, if it passed, compute the shared secret and calculate the session keys.
return false;
}
+ if(!s->initiator && !send_sig(s)) {
+ return false;
+ }
+
free(s->mykex);
free(s->hiskex);
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one));
#endif
+ sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
+
char data[4096];
int version;
return false;
}
- sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
-
if(!recvline(fd, line, sizeof(line)) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) {
if(verbose) {
fprintf(stderr, "Could not fully establish control socket connection\n");
invite-tinc-up.test \
ns-ping.test \
scripts.test \
+ security.test \
sptps-basic.test \
variables.test
AM_CFLAGS = -iquote.
+check_PROGRAMS = \
+ splice
+
+splice_SOURCES = splice.c
+
clean-local:
-for pid in *.test.?/pid; do ../src/tinc --pidfile="$$pid" stop; done
-killall ../src/sptps_test
--- /dev/null
+#!/bin/sh
+
+. "${0%/*}/testlib.sh"
+
+# Skip this test if tools are missing
+
+which socket >/dev/null || exit 77
+which timeout >/dev/null || exit 77
+
+# Initialize two nodes
+
+$tinc $c1 <<EOF
+init foo
+set DeviceType dummy
+set Port 32754
+set Address localhost
+set PingTimeout 1
+set AutoConnect no
+EOF
+
+$tinc $c2 <<EOF
+init bar
+set DeviceType dummy
+set Port 32755
+set PingTimeout 1
+set MaxTimeout 1
+set ExperimentalProtocol no
+set AutoConnect no
+EOF
+
+# Exchange host config files
+
+$tinc $c1 export | $tinc $c2 exchange | $tinc $c1 import
+
+$tinc $c1 start $r1
+$tinc $c2 start $r2
+
+# No ID sent by responding node if we don't send an ID first, before the timeout
+
+result=`(sleep 2; echo "0 bar 17.7") | timeout 3 socket localhost 32754` && exit 1
+test $? = 124
+test -z "$result"
+
+# ID sent if initiator sends first, but still tarpitted
+
+result=`echo "0 bar 17.7" | timeout 3 socket localhost 32754` && exit 1
+test $? = 124
+test "`echo "$result" | head -c 10`" = "0 foo 17.7"
+
+# No invalid IDs allowed
+
+result=`echo "0 foo 17.7" | timeout 1 socket localhost 32754` && exit 1
+test $? = 124
+test -z "$result"
+
+result=`echo "0 baz 17.7" | timeout 1 socket localhost 32754` && exit 1
+test $? = 124
+test -z "$result"
+
+# No NULL METAKEYs allowed
+
+result=`printf "0 foo 17.0\n1 0 672 0 0 834188619F4D943FD0F4B1336F428BD4AC06171FEABA66BD2356BC9593F0ECD643F0E4B748C670D7750DFDE75DC9F1D8F65AB1026F5ED2A176466FBA4167CC567A2085ABD070C1545B180BDA86020E275EA9335F509C57786F4ED2378EFFF331869B856DDE1C05C461E4EECAF0E2FB97AF77B7BC2AD1B34C12992E45F5D1254BBF0C3FB224ABB3E8859594A83B6CA393ED81ECAC9221CE6BC71A727BCAD87DD80FC0834B87BADB5CB8FD3F08BEF90115A8DF1923D7CD9529729F27E1B8ABD83C4CF8818AE10257162E0057A658E265610B71F9BA4B365A20C70578FAC65B51B91100392171BA12A440A5E93C4AA62E0C9B6FC9B68F953514AAA7831B4B2C31C4\n" | timeout 3 socket localhost 32755` && exit 1
+test $? = 124
+test -z "$result" # Not even the ID should be sent when the first packet contains illegal data
+
+# No splicing allowed
+
+$tinc $c2 stop
+$tinc $c2 del ExperimentalProtocol
+$tinc $c2 start $r2
+
+./splice foo localhost 32754 bar localhost 32755 17.7 &
+sleep 3
+test `$tinc $c1 dump reachable nodes | wc -l` = 1
+test `$tinc $c2 dump reachable nodes | wc -l` = 1
+kill $!
+
+$tinc $c2 stop
+$tinc $c1 stop
+
+# Test splicing again with legacy protocol
+
+$tinc $c1 set ExperimentalProtocol no
+$tinc $c2 set ExperimentalProtocol no
+
+$tinc $c1 start $r1
+$tinc $c2 start $r2
+
+./splice foo localhost 32754 bar localhost 32755 17.0 &
+sleep 3
+test `$tinc $c1 dump reachable nodes | wc -l` = 1
+test `$tinc $c2 dump reachable nodes | wc -l` = 1
+kill $!
+
+# Clean up
+
+$tinc $c2 stop
+$tinc $c1 stop
--- /dev/null
+/*
+ splice.c -- Splice two outgoing tinc connections together
+ Copyright (C) 2018 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 <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#ifdef HAVE_MINGW
+extern const char *winerror(int);
+#define strerror(x) ((x)>0?strerror(x):winerror(GetLastError()))
+#define sockerrno WSAGetLastError()
+#define sockstrerror(x) winerror(x)
+#else
+#define sockerrno errno
+#define sockstrerror(x) strerror(x)
+#endif
+
+int main(int argc, char *argv[]) {
+ if(argc < 7) {
+ fprintf(stderr, "Usage: %s name1 host1 port1 name2 host2 port2 [protocol]\n", argv[0]);
+ return 1;
+ }
+
+ const char *protocol;
+
+ if(argc >= 8) {
+ protocol = argv[7];
+ } else {
+ protocol = "17.7";
+ }
+
+#ifdef HAVE_MINGW
+ static struct WSAData wsa_state;
+
+ if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
+ return 1;
+ }
+
+#endif
+ int sock[2];
+ char buf[1024];
+
+ struct addrinfo *ai, hint;
+ memset(&hint, 0, sizeof(hint));
+
+ hint.ai_family = AF_UNSPEC;
+ hint.ai_socktype = SOCK_STREAM;
+ hint.ai_protocol = IPPROTO_TCP;
+ hint.ai_flags = 0;
+
+ for (int i = 0; i < 2; i++) {
+ if(getaddrinfo(argv[2 + 3 * i], argv[3 + 3 * i], &hint, &ai) || !ai) {
+ fprintf(stderr, "getaddrinfo() failed: %s\n", sockstrerror(sockerrno));
+ return 1;
+ }
+
+ sock[i] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+
+ if(sock[i] == -1) {
+ fprintf(stderr, "Could not create socket: %s\n", sockstrerror(sockerrno));
+ return 1;
+ }
+
+ if(connect(sock[i], ai->ai_addr, ai->ai_addrlen)) {
+ fprintf(stderr, "Could not connect to %s: %s\n", argv[i + 3 * i], sockstrerror(sockerrno));
+ return 1;
+ }
+
+ fprintf(stderr, "Connected to %s\n", argv[1 + 3 * i]);
+
+ /* Pretend to be the other one */
+ int len = snprintf(buf, sizeof buf, "0 %s %s\n", argv[4 - 3 * i], protocol);
+ if (send(sock[i], buf, len, 0) != len) {
+ fprintf(stderr, "Error sending data to %s: %s\n", argv[1 + 3 * i], sockstrerror(sockerrno));
+ return 1;
+ }
+
+ /* Ignore the response */
+ do {
+ if (recv(sock[i], buf, 1, 0) != 1) {
+ fprintf(stderr, "Error reading data from %s: %s\n", argv[1 + 3 * i], sockstrerror(sockerrno));
+ return 1;
+ }
+ } while(*buf != '\n');
+ }
+
+ fprintf(stderr, "Splicing...\n");
+
+ int nfds = (sock[0] > sock[1] ? sock[0] : sock[1]) + 1;
+
+ while(true) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(sock[0], &fds);
+ FD_SET(sock[1], &fds);
+
+ if(select(nfds, &fds, NULL, NULL, NULL) <= 0) {
+ return 1;
+ }
+
+ for(int i = 0; i < 2; i++ ) {
+ if(FD_ISSET(sock[i], &fds)) {
+ ssize_t len = recv(sock[i], buf, sizeof buf, 0);
+
+ if(len < 0) {
+ fprintf(stderr, "Error while reading from %s: %s\n", argv[1 + i * 3], sockstrerror(sockerrno));
+ return 1;
+ }
+
+ if(len == 0) {
+ fprintf(stderr, "Connection closed by %s\n", argv[1 + i * 3]);
+ return 0;
+ }
+
+ if(send(sock[i ^ 1], buf, len, 0) != len) {
+ fprintf(stderr, "Error while writing to %s: %s\n", argv[4 - i * 3], sockstrerror(sockerrno));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}