#include "netutl.h"
#include "xalloc.h"
-static const unsigned int NOT_CACHED = -1;
+static const unsigned int NOT_CACHED = UINT_MAX;
// Find edges pointing to this node, and use them to build a list of unique, known addresses.
static struct addrinfo *get_known_addresses(node_t *n) {
return;
}
+ logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Caching recent address for %s", cache->node->name);
+
// Shift everything, move/add the address to the first slot
if(pos == NOT_CACHED) {
if(cache->data.used < MAX_CACHED_ADDRESSES) {
exit_configuration(cache->config_tree);
cache->config_tree = NULL;
- return false;
+ return NULL;
}
address_cache_t *open_address_cache(node_t *node) {
return cache;
}
-void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa) {
- if(sa) {
- add_recent_address(cache, sa);
- }
-
+void reset_address_cache(address_cache_t *cache) {
if(cache->config_tree) {
exit_configuration(cache->config_tree);
cache->config_tree = NULL;
void close_address_cache(address_cache_t *cache);
address_cache_t *open_address_cache(node_t *node) ATTR_DEALLOCATOR(close_address_cache);
-void reset_address_cache(address_cache_t *cache, const sockaddr_t *sa);
+void reset_address_cache(address_cache_t *cache);
#endif
#include "script.h"
#include "subnet.h"
#include "xalloc.h"
+#include "address_cache.h"
/* Implementation of Kruskal's algorithm.
Running time: O(EN)
if(n != myself) {
became_reachable_count++;
+
+ if(n->connection && n->connection->outgoing) {
+ if(!n->address_cache) {
+ n->address_cache = open_address_cache(n);
+ }
+
+ add_recent_address(n->address_cache, &n->connection->address);
+ }
}
} else {
logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable",
}
}
- snprintf(filename, sizeof(filename), "%s" SLASH "invitations", confbase);
-
- if(mkdir(filename, 0700) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", filename, strerror(errno));
- return 1;
+ if(!makedirs(DIR_INVITATIONS)) {
+ return false;
}
+ snprintf(filename, sizeof(filename), "%s" SLASH "invitations", confbase);
+
// Count the number of valid invitations, clean up old ones
DIR *dir = opendir(filename);
goto make_names;
}
- if(mkdir(confbase, 0777) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
- return false;
- }
-
- if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
+ if(!makedirs(DIR_HOSTS | DIR_CONFBASE | DIR_CACHE)) {
return false;
}
}
// Make sure confbase exists and is accessible.
- if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
- return 1;
- }
-
- if(mkdir(confbase, 0777) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
- return 1;
+ if(!makedirs(DIR_CONFDIR | DIR_CONFBASE)) {
+ return false;
}
if(access(confbase, R_OK | W_OK | X_OK)) {
n->address_cache = open_address_cache(n);
}
- reset_address_cache(n->address_cache, &n->address);
+ if(n->connection && n->connection->edge) {
+ reset_address_cache(n->address_cache);
+ add_recent_address(n->address_cache, &n->connection->edge->address);
+ }
}
// Reset the UDP ping timer.
read_host_config(&config, ent->d_name, true);
if(!n) {
- n = new_node();
- n->name = xstrdup(ent->d_name);
+ n = new_node(ent->d_name);
node_add(n);
}
}
myname = xstrdup(name);
- myself = new_node();
+ myself = new_node(name);
myself->connection = new_connection();
- myself->name = name;
- myself->connection->name = xstrdup(name);
+ myself->connection->name = name;
read_host_config(&config_tree, name, true);
if(!get_config_string(lookup_config(&config_tree, "Port"), &myport.tcp)) {
n->address_cache = open_address_cache(n);
}
- if(n->connection) {
- logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", n->name);
-
- if(!n->connection->outgoing) {
- n->connection->outgoing = outgoing;
- return;
- } else {
- goto remove;
- }
+ if(!n->connection) {
+ do_outgoing_connection(outgoing);
+ return;
}
- do_outgoing_connection(outgoing);
- return;
+ logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", n->name);
-remove:
- list_delete(&outgoing_list, outgoing);
+ if(n->connection->outgoing) {
+ list_delete(&outgoing_list, outgoing);
+ } else {
+ n->connection->outgoing = outgoing;
+ }
}
static bool check_tarpit(const sockaddr_t *sa, int fd) {
node_t *n = lookup_node(name);
if(!n) {
- n = new_node();
- n->name = xstrdup(name);
+ n = new_node(name);
node_add(n);
}
splay_empty_tree(&node_tree);
}
-node_t *new_node(void) {
+node_t *new_node(const char *name) {
node_t *n = xzalloc(sizeof(*n));
if(replaywin) {
n->mtu = MTU;
n->maxmtu = MTU;
n->udp_ping_rtt = -1;
+ n->name = xstrdup(name);
return n;
}
extern void exit_nodes(void);
extern void free_node(node_t *n);
-extern node_t *new_node(void) ATTR_MALLOC ATTR_DEALLOCATOR(free_node);
+extern node_t *new_node(const char *name) ATTR_MALLOC ATTR_DEALLOCATOR(free_node);
extern void node_add(node_t *n);
extern void node_del(node_t *n);
extern node_t *lookup_node(char *name);
#include "random.h"
#include "compression.h"
#include "proxy.h"
+#include "address_cache.h"
#include "ed25519/sha512.h"
#include "keys.h"
logger(DEBUG_CONNECTIONS, LOG_INFO, "Key successfully received from %s (%s)", c->name, c->hostname);
+ if(!c->node) {
+ c->node = lookup_node(c->name);
+ }
+
+ if(!c->node) {
+ c->node = new_node(c->name);
+ c->node->connection = c;
+ node_add(c->node);
+ }
+
+ if(!c->node->address_cache) {
+ c->node->address_cache = open_address_cache(c->node);
+ }
+
+ add_recent_address(c->node->address_cache, &c->address);
+
// Call invitation-accepted script
environment_t env;
char *address, *port;
n = lookup_node(c->name);
if(!n) {
- n = new_node();
- n->name = xstrdup(c->name);
+ n = new_node(c->name);
node_add(n);
} else {
if(n->connection) {
}
if(!from) {
- from = new_node();
- from->name = xstrdup(from_name);
+ from = new_node(from_name);
node_add(from);
}
if(!to) {
- to = new_node();
- to->name = xstrdup(to_name);
+ to = new_node(to_name);
node_add(to);
}
if(c->outgoing && c->outgoing->timeout) {
c->outgoing->timeout = 0;
- reset_address_cache(c->outgoing->node->address_cache, &c->address);
+ reset_address_cache(c->node->address_cache);
+ add_recent_address(c->node->address_cache, &c->address);
}
return true;
#include "protocol.h"
#include "subnet.h"
#include "utils.h"
-#include "xalloc.h"
bool send_add_subnet(connection_t *c, const subnet_t *subnet) {
char netstr[MAXNETSTR];
}
if(!owner) {
- owner = new_node();
- owner->name = xstrdup(name);
+ owner = new_node(name);
node_add(owner);
}
return 0;
}
+static bool makedir(const char *path, mode_t mode) {
+ if(mkdir(path, mode) && errno != EEXIST) {
+ fprintf(stderr, "Could not create directory %s: %s\n", path, strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool makedirs(tincd_dir_t dirs) {
+ if(dirs & DIR_CONFBASE && !makedir(confbase, 0777)) {
+ return false;
+ }
+
+ if(dirs & DIR_CONFDIR && !confbase_given && !makedir(confdir, 0755)) {
+ return false;
+ }
+
+ if(dirs & DIR_HOSTS && !makedir(hosts_dir, 0777)) {
+ return false;
+ }
+
+ char path[PATH_MAX];
+
+ if(dirs & DIR_INVITATIONS) {
+ snprintf(path, sizeof(path), "%s" SLASH "invitations", confbase);
+
+ if(!makedir(path, 0700)) {
+ return false;
+ }
+ }
+
+ if(dirs & DIR_CACHE) {
+ snprintf(path, sizeof(path), "%s" SLASH "%s", confbase, "cache");
+
+ if(!makedir(path, 0755)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
static int cmd_init(int argc, char *argv[]) {
if(!access(tinc_conf, F_OK)) {
fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
return 1;
}
- if(!confbase_given && mkdir(confdir, 0755) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", confdir, strerror(errno));
- return 1;
- }
-
- if(mkdir(confbase, 0777) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno));
- return 1;
- }
-
- if(mkdir(hosts_dir, 0777) && errno != EEXIST) {
- fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno));
- return 1;
+ if(!makedirs(DIR_HOSTS | DIR_CONFBASE | DIR_CONFDIR | DIR_CACHE)) {
+ return false;
}
FILE *f = fopen(tinc_conf, "w");
int type;
} var_t;
+typedef enum {
+ DIR_CACHE = 1 << 0,
+ DIR_CONFBASE = 1 << 1,
+ DIR_CONFDIR = 1 << 2,
+ DIR_HOSTS = 1 << 3,
+ DIR_INVITATIONS = 1 << 4,
+} tincd_dir_t;
+
extern const var_t variables[];
extern size_t rstrip(char *value);
extern bool sendline(int fd, const char *format, ...) ATTR_FORMAT(printf, 2, 3);
extern bool recvline(int fd, char *line, size_t len);
extern int check_port(const char *name);
+extern bool makedirs(tincd_dir_t dirs);
#endif
--- /dev/null
+#!/usr/bin/env python3
+
+"""Test recent address cache."""
+
+import os
+import typing as T
+import shutil
+
+from testlib import check
+from testlib.log import log
+from testlib.proc import Tinc, Script
+from testlib.test import Test
+
+
+def init(ctx: Test) -> T.Tuple[Tinc, Tinc]:
+ """Create test node."""
+ foo, bar = ctx.node(), ctx.node()
+
+ stdin = f"""
+ init {foo}
+ set Port 0
+ set Address localhost
+ set DeviceType dummy
+ set AutoConnect no
+ """
+ foo.cmd(stdin=stdin)
+
+ return foo, bar
+
+
+def connect_nodes(foo: Tinc, bar: Tinc) -> None:
+ """Start second node and wait for connection."""
+ log.info("connect nodes")
+ bar.cmd("start")
+ bar[foo.script_up].wait()
+ foo[bar.script_up].wait()
+
+
+def run_tests(ctx: Test) -> None:
+ """Run tests."""
+ foo, bar = init(ctx)
+
+ log.info("cache directory must exist after init")
+ check.dir_exists(foo.sub("cache"))
+
+ foo.add_script(Script.TINC_UP)
+ foo.add_script(Script.INVITATION_ACCEPTED)
+ foo.start()
+
+ log.info("invite %s to %s", bar, foo)
+ invite, _ = foo.cmd("invite", bar.name)
+ invite = invite.strip()
+
+ log.info("join %s to %s", bar, foo)
+ bar.cmd("join", invite)
+
+ log.info("cache directory must exist after join")
+ check.dir_exists(bar.sub("cache"))
+
+ log.info("invitee address must be cached after invitation is accepted")
+ foo[Script.INVITATION_ACCEPTED].wait()
+ check.file_exists(foo.sub(f"cache/{bar}"))
+ os.remove(foo.sub(f"cache/{bar}"))
+
+ log.info("configure %s", bar)
+ bar.cmd("set", "DeviceType", "dummy")
+ bar.cmd("set", "Port", "0")
+
+ log.info("add host-up scripts")
+ foo.add_script(bar.script_up)
+ bar.add_script(foo.script_up)
+
+ connect_nodes(foo, bar)
+
+ log.info("%s must cache %s's public address", bar, foo)
+ check.file_exists(bar.sub(f"cache/{foo}"))
+
+ log.info("%s must not cache %s's outgoing address", foo, bar)
+ assert not os.path.exists(foo.sub(f"cache/{bar}"))
+
+ log.info("stop node %s", bar)
+ bar.cmd("stop")
+
+ log.info("remove %s cache directory", bar)
+ shutil.rmtree(bar.sub("cache"))
+
+ connect_nodes(foo, bar)
+
+ log.info("make sure %s cache was recreated", bar)
+ check.file_exists(bar.sub(f"cache/{foo}"))
+
+ log.info("stop nodes")
+ bar.cmd("stop")
+ foo.cmd("stop")
+
+ log.info("remove Address from all nodes")
+ for node in foo, bar:
+ node.cmd("del", "Address", code=None)
+ for peer in foo, bar:
+ node.cmd("del", f"{peer}.Address", code=None)
+ bar.cmd("add", "ConnectTo", foo.name)
+
+ log.info("make sure connection works using just the cached address")
+ foo.cmd("start")
+ connect_nodes(foo, bar)
+
+
+with Test("run address cache tests") as context:
+ run_tests(context)
check.equals(node, bar.name)
-def run_unconnected_tests(foo: Tinc) -> None:
+def run_unconnected_tests(foo: Tinc, bar: Tinc) -> None:
"""Run online tests with unconnected nodes."""
log.info("dump invalid type")
check.lines(out, 1)
check.is_in("<control>", out)
- log.info("dump unconnected nodes")
- for arg in (("nodes",), ("reachable", "nodes")):
- out, _ = foo.cmd("dump", *arg)
- check.lines(out, 1)
- check.is_in(f"{foo} id ", out)
+ log.info("%s knows about %s", foo, bar)
+ out, _ = foo.cmd("dump", "nodes")
+ check.lines(out, 2)
+ check.is_in(f"{foo} id ", out)
+ check.is_in(f"{bar} id ", out)
+
+ log.info("%s can only reach itself", foo)
+ out, _ = foo.cmd("dump", "reachable", "nodes")
+ check.lines(out, 1)
+ check.is_in(f"{foo} id ", out)
def run_connected_tests(foo: Tinc, bar: Tinc) -> None:
for sub in SUBNETS_BAR:
bar.cmd("add", "Subnet", sub)
- run_unconnected_tests(foo)
+ run_unconnected_tests(foo, bar)
log.info("start %s", bar)
foo.add_script(bar.script_up)
check.is_in("already exists", err)
if os.name != "nt":
+ log.info("bad permissions on invitations are fixed")
invites = foo.sub("invitations")
os.chmod(invites, 0)
- _, err = foo.cmd("invite", "foobar", code=1)
- check.is_in("Could not read directory", err)
- os.chmod(invites, 0o750)
+ out, _ = foo.cmd("invite", "foobar")
+ check.has_prefix(out, "localhost:")
- log.info("block creating invitations directory")
- shutil.rmtree(foo.sub("invitations"))
+ log.info("invitations directory is created with bad permissions on parent")
+ shutil.rmtree(invites)
os.chmod(foo.work_dir, 0o500)
- _, err = foo.cmd("invite", "foobar", code=1)
- check.is_in("Could not create directory", err)
- os.chmod(foo.work_dir, 0o750)
+ out, _ = foo.cmd("invite", "foobar")
+ check.has_prefix(out, "localhost:")
+ check.true(os.access(invites, os.W_OK))
log.info("fully block access to configuration directory")
work_dir = foo.sub("test_no_access")
check.is_in("Could not connect to", err)
if os.name != "nt":
- log.info("test working without access to configuration directory")
+ log.info("bad permissions on configuration directory are fixed")
work_dir = foo.sub("wd_access_test")
os.mkdir(work_dir, mode=400)
_, err = foo.cmd("-c", work_dir, "join", FAKE_INVITE, code=1)
- check.is_in("No permission to write", err)
+ check.is_in("Could not connect to", err)
+ check.true(os.access(work_dir, mode=os.W_OK))
with Test("run invite success tests") as context:
tests = [
+ 'address_cache.py',
'basic.py',
'cmd_dump.py',
'cmd_fsck.py',
def file_exists(path: T.Union[str, Path]) -> None:
- """Check that file or directory exists."""
- if not os.path.exists(path):
- raise ValueError("expected path '{path}' to exist")
+ """Check that file exists."""
+ if not os.path.isfile(path):
+ raise ValueError(f"expected file '{path}' to exist")
+
+
+def dir_exists(path: T.Union[str, Path]) -> None:
+ """Check that directory exists."""
+ if not os.path.isdir(path):
+ raise ValueError(f"expected directory '{path}' to exist")
}
static node_t *make_node(const char *name) {
- node_t *node = new_node();
- node->name = xstrdup(name);
+ node_t *node = new_node(name);
node->status.reachable = true;
node_add(node);
return node;
static int setup(void **state) {
(void)state;
- myself = new_node();
- myself->name = xstrdup("myself");
+ myself = new_node("myself");
return 0;
}