.Va PrivateKeyFile
specified in the configuration file.
+.It Va ProcessPriority Li = low | normal | high
+When this option is used the priority of the tincd process will be adjusted.
+Increasing the priority may help to reduce latency and packet loss on the VPN.
+
.It Va TunnelServer Li = yes | no Po no Pc Bq experimental
When this option is enabled tinc will no longer forward information between other tinc daemons,
and will only allow nodes and subnets on the VPN which are present in the
@subsection Configuration of Darwin (MacOS/X) kernels
Tinc on Darwin relies on a tunnel driver for its data acquisition from the kernel.
-Tinc supports either the driver from @uref{http://www-user.rhrk.uni-kl.de/~nissler/tuntap/},
+Tinc supports either the driver from @uref{http://tuntaposx.sourceforge.net/},
which supports both tun and tap style devices,
and also the driver from from @uref{http://chrisp.de/en/projects/tunnel.html}.
The former driver is recommended.
or PrivateKeyFile
specified in the configuration file.
+@cindex ProcessPriority
+@item ProcessPriority = <low|normal|high>
+When this option is used the priority of the tincd process will be adjusted.
+Increasing the priority may help to reduce latency and packet loss on the VPN.
+
@cindex TunnelServer
@item TunnelServer = <yes|no> (no) [experimental]
When this option is enabled tinc will no longer forward information between other tinc daemons,
Disables encryption and authentication.
Only useful for debugging.
+@item -R, --chroot
+Change process root directory to the directory where the config file is
+located (@file{@value{sysconfdir}/tinc/@var{netname}/} as determined by
+-n/--net option or as given by -c/--config option), for added security.
+The chroot is performed after all the initialization is done, after
+writing pid files and opening network sockets.
+
+Note that this option alone does not do any good without -U/--user, below.
+
+Note also that tinc can't run scripts anymore (such as tinc-down or host-up),
+unless it's setup to be runnable inside chroot environment.
+
+@item -U, --user=@var{user}
+Switch to the given @var{user} after initialization, at the same time as
+chroot is performed (see --chroot above). With this option tinc drops
+privileges, for added security.
+
@item --help
Display a short reminder of these runtime options and terminate.
.Nd tinc VPN daemon
.Sh SYNOPSIS
.Nm
-.Op Fl cdDKnL
+.Op Fl cdDKnLRU
.Op Fl -config Ns = Ns Ar DIR
.Op Fl -no-detach
.Op Fl -debug Ns Op = Ns Ar LEVEL
.Op Fl -mlock
.Op Fl -logfile Ns Op = Ns Ar FILE
.Op Fl -bypass-security
+.Op Fl -chroot
+.Op Fl -user Ns = Ns Ar USER
.Op Fl -help
.Op Fl -version
.Sh DESCRIPTION
.It Fl -bypass-security
Disables encryption and authentication of the meta protocol.
Only useful for debugging.
+.It Fl -chroot
+With this option tinc chroots into the directory where network
+config is located (@sysconfdir@/tinc/NETNAME if -n option is used,
+or to the directory specified with -c option) after initialization.
+.It Fl -user Ns = Ns Ar USER
+setuid to the specified
+.Ar USER
+after initialization.
.It Fl -help
Display short list of options.
.It Fl -version
volatile int cp_index = 0;
#endif
-char *hexadecimals = "0123456789ABCDEF";
+const char hexadecimals[] = "0123456789ABCDEF";
int charhex2bin(char c) {
if(isdigit(c))
#include <w32api/windows.h>
#endif
-char *winerror(int err) {
+const char *winerror(int err) {
static char buf[1024], *newline;
if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
extern void bin2hex(char *src, char *dst, int length);
#ifdef HAVE_MINGW
-extern char *winerror(int);
+extern const char *winerror(int);
#define strerror(x) ((x)>0?strerror(x):winerror(GetLastError()))
#endif
e->to->via = indirect ? n->via : e->to;
e->to->options = e->options;
- if(sockaddrcmp(&e->to->address, &e->address)) {
- node = splay_unlink(node_udp_tree, e->to);
- sockaddrfree(&e->to->address);
- sockaddrcpy(&e->to->address, &e->address);
-
- if(e->to->hostname)
- free(e->to->hostname);
-
- e->to->hostname = sockaddr2hostname(&e->to->address);
-
- if(node)
- splay_insert_node(node_udp_tree, node);
-
- if(e->to->options & OPTION_PMTU_DISCOVERY) {
- e->to->mtuprobes = 0;
- e->to->minmtu = 0;
- e->to->maxmtu = MTU;
- if(e->to->status.validkey)
- send_mtu_probe(e->to);
- }
- }
+ if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
+ update_node_udp(e->to, &e->address);
list_insert_tail(todo_list, e->to);
}
if(n->status.reachable) {
ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became reachable"),
n->name, n->hostname);
- splay_insert(node_udp_tree, n);
} else {
ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became unreachable"),
n->name, n->hostname);
- splay_delete(node_udp_tree, n);
}
+ /* TODO: only clear status.validkey if node is unreachable? */
+
n->status.validkey = false;
n->status.waitingforkey = false;
if(!get_config_string(lookup_config(config_tree, "Interface"), &iface))
#ifdef HAVE_LINUX_IF_TUN_H
- iface = xstrdup(netname);
+ if (netname != NULL)
+ iface = xstrdup(netname);
#else
iface = xstrdup(rindex(device, '/') ? rindex(device, '/') + 1 : device);
#endif
extern void send_packet(const struct node_t *, vpn_packet_t *);
extern void receive_tcppacket(struct connection_t *, char *, int);
extern void broadcast_packet(const struct node_t *, vpn_packet_t *);
-extern bool setup_network_connections(void);
+extern bool setup_network(void);
extern void setup_outgoing_connection(struct outgoing_t *);
extern void try_outgoing_connections(void);
extern void close_network_connections(void);
#endif
int keylifetime = 0;
+int keyexpires = 0;
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 *);
send_mtu_probe_handler(0, 0, n);
}
-void mtu_probe_h(node_t *n, vpn_packet_t *packet) {
+void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) {
ifdebug(TRAFFIC) logger(LOG_INFO, _("Got MTU probe length %d from %s (%s)"), packet->len, n->name, n->hostname);
if(!packet->data[0]) {
packet->data[0] = 1;
send_packet(n, packet);
} else {
- if(n->minmtu < packet->len)
- n->minmtu = packet->len;
+ if(n->minmtu < len)
+ n->minmtu = len;
}
}
route(n, packet);
}
-static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
+static bool try_mac(node_t *n, const vpn_packet_t *inpkt)
+{
+ if(!digest_active(&n->indigest) || !n->inmaclength || inpkt->len < sizeof inpkt->seqno + n->inmaclength)
+ return false;
+
+ return digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
+}
+
+static void 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;
cp();
+ if(!cipher_active(&n->incipher)) {
+ ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got packet from %s (%s) but he hasn't got our key yet"),
+ n->name, n->hostname);
+ return;
+ }
+
/* Check packet length */
- if(inpkt->len < sizeof inpkt->seqno + digest_length(&myself->digest)) {
+ if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) {
ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got too short packet from %s (%s)"),
n->name, n->hostname);
return;
/* Check the message authentication code */
- if(digest_active(&myself->digest) && !digest_verify(&myself->digest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len)) {
+ if(digest_active(&n->indigest) && !digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len)) {
ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Got unauthenticated packet from %s (%s)"), n->name, n->hostname);
return;
}
/* Decrypt the packet */
- if(cipher_active(&myself->cipher)) {
+ if(cipher_active(&n->incipher)) {
outpkt = pkt[nextpkt++];
outlen = MAXSIZE;
- if(!cipher_decrypt(&myself->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+ if(!cipher_decrypt(&n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Error decrypting packet from %s (%s)"), n->name, n->hostname);
return;
}
/* Decompress the packet */
- if(myself->compression) {
+ length_t origlen = inpkt->len;
+
+ if(n->incompression) {
outpkt = pkt[nextpkt++];
- if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, myself->compression)) < 0) {
+ if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression)) < 0) {
ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while uncompressing packet from %s (%s)"),
n->name, n->hostname);
return;
}
inpkt = outpkt;
+
+ origlen -= MTU/64 + 20;
}
inpkt->priority = 0;
if(!inpkt->data[12] && !inpkt->data[13])
- mtu_probe_h(n, inpkt);
+ mtu_probe_h(n, inpkt, origlen);
else
receive_packet(n, inpkt);
}
n->name, n->hostname);
if(!n->status.waitingforkey)
- send_req_key(n->nexthop->connection, myself, n);
+ send_req_key(n);
n->status.waitingforkey = true;
return;
}
- if(!n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
+ if(n->options & OPTION_PMTU_DISCOVERY && !n->minmtu && (inpkt->data[12] | inpkt->data[13])) {
ifdebug(TRAFFIC) logger(LOG_INFO,
_("No minimum MTU established yet for %s (%s), forwarding via TCP"),
n->name, n->hostname);
send_tcppacket(n->nexthop->connection, origpkt);
+
+ return;
}
origlen = inpkt->len;
/* Compress the packet */
- if(n->compression) {
+ if(n->outcompression) {
outpkt = pkt[nextpkt++];
- if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->compression)) < 0) {
+ if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) {
ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while compressing packet to %s (%s)"),
n->name, n->hostname);
return;
/* Encrypt the packet */
- if(cipher_active(&n->cipher)) {
+ if(cipher_active(&n->outcipher)) {
outpkt = pkt[nextpkt++];
outlen = MAXSIZE;
- if(!cipher_encrypt(&n->cipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
+ if(!cipher_encrypt(&n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) {
ifdebug(TRAFFIC) logger(LOG_ERR, _("Error while encrypting packet to %s (%s)"), n->name, n->hostname);
goto end;
}
/* Add the message authentication code */
- if(digest_active(&n->digest)) {
- digest_create(&n->digest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
- inpkt->len += digest_length(&n->digest);
+ if(digest_active(&n->outdigest)) {
+ digest_create(&n->outdigest, &inpkt->seqno, inpkt->len, &inpkt->seqno + inpkt->len);
+ inpkt->len += digest_length(&n->outdigest);
}
/* Determine which socket we have to use */
ifdebug(TRAFFIC) logger(LOG_INFO, _("Broadcasting packet of %d bytes from %s (%s)"),
packet->len, from->name, from->hostname);
- if(from != myself)
+ if(from != myself) {
send_packet(myself, packet);
+ // In TunnelServer mode, do not forward broadcast packets.
+ // The MST might not be valid and create loops.
+ if(tunnelserver)
+ return;
+ }
+
for(node = connection_tree->head; node; node = node->next) {
c = node->data;
}
}
+static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
+ splay_node_t *node;
+ edge_t *e;
+ node_t *n = NULL;
+
+ for(node = edge_weight_tree->head; node; node = node->next) {
+ e = node->data;
+
+ if(sockaddrcmp_noport(from, &e->address))
+ continue;
+
+ if(!n)
+ n = e->to;
+
+ if(!try_mac(e->to, pkt))
+ continue;
+
+ n = e->to;
+ break;
+ }
+
+ return n;
+}
+
void handle_incoming_vpn_data(int sock, short events, void *data)
{
vpn_packet_t pkt;
n = lookup_node_udp(&from);
if(!n) {
- hostname = sockaddr2hostname(&from);
- logger(LOG_WARNING, _("Received UDP packet from unknown source %s"),
- hostname);
- free(hostname);
- return;
+ n = try_harder(&from, &pkt);
+ if(n)
+ update_node_udp(n, &from);
+ else ifdebug(PROTOCOL) {
+ hostname = sockaddr2hostname(&from);
+ logger(LOG_WARNING, _("Received UDP packet from unknown source %s"), hostname);
+ free(hostname);
+ return;
+ }
+ else
+ return;
}
receive_udppacket(n, &pkt);
}
void regenerate_key() {
- ifdebug(STATUS) logger(LOG_INFO, _("Regenerating symmetric key"));
-
- if(!cipher_regenerate_key(&myself->cipher, true)) {
- logger(LOG_ERR, _("Error regenerating key!"));
- abort();
- }
-
if(timeout_initialized(&keyexpire_event)) {
+ ifdebug(STATUS) logger(LOG_INFO, _("Expiring symmetric keys"));
event_del(&keyexpire_event);
send_key_changed(broadcast, myself);
} else {
#if !defined(SOL_IP) || !defined(IP_TOS)
if(priorityinheritance)
- logger(LOG_WARNING, _("PriorityInheritance not supported on this platform"));
+ logger(LOG_WARNING, _("%s not supported on this platform"), "PriorityInheritance");
#endif
if(!get_config_int(lookup_config(config_tree, "MACExpire"), &macexpire))
if(!get_config_string(lookup_config(myself->connection->config_tree, "Cipher"), &cipher))
cipher = xstrdup("blowfish");
- if(!cipher_open_by_name(&myself->cipher, cipher)) {
+ if(!cipher_open_by_name(&myself->incipher, cipher)) {
logger(LOG_ERR, _("Unrecognized cipher type!"));
return false;
}
if(!get_config_string(lookup_config(myself->connection->config_tree, "Digest"), &digest))
digest = xstrdup("sha1");
- if(!digest_open_by_name(&myself->digest, digest)) {
+ if(!digest_open_by_name(&myself->indigest, digest)) {
logger(LOG_ERR, _("Unrecognized digest type!"));
return false;
}
- if(!get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->maclength))
+ if(!get_config_int(lookup_config(myself->connection->config_tree, "MACLength"), &myself->inmaclength))
- if(digest_active(&myself->digest)) {
- if(myself->maclength > digest_length(&myself->digest)) {
+ if(digest_active(&myself->indigest)) {
+ if(myself->inmaclength > digest_length(&myself->indigest)) {
logger(LOG_ERR, _("MAC length exceeds size of digest!"));
return false;
- } else if(myself->maclength < 0) {
+ } else if(myself->inmaclength < 0) {
logger(LOG_ERR, _("Bogus MAC length!"));
return false;
}
/* Compression */
- if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), &myself->compression)) {
- if(myself->compression < 0 || myself->compression > 11) {
+ if(get_config_int(lookup_config(myself->connection->config_tree, "Compression"), &myself->incompression)) {
+ if(myself->incompression < 0 || myself->incompression > 11) {
logger(LOG_ERR, _("Bogus compression level!"));
return false;
}
} else
- myself->compression = 0;
+ myself->incompression = 0;
myself->connection->outcompression = 0;
}
/*
- setup all initial network connections
+ initialize network
*/
-bool setup_network_connections(void) {
+bool setup_network(void)
+{
cp();
init_connections();
if(!setup_myself())
return false;
- try_outgoing_connections();
-
return true;
}
#include "utils.h"
#include "xalloc.h"
+#include <assert.h>
+
#ifdef WSAEINPROGRESS
#define EINPROGRESS WSAEINPROGRESS
#endif
#endif
}
-int setup_listen_socket(const sockaddr_t *sa) {
+static bool bind_to_interface(int sd) { /* {{{ */
+ char *iface;
+
+#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
+ struct ifreq ifr;
+ int status;
+#endif /* defined(SOL_SOCKET) && defined(SO_BINDTODEVICE) */
+
+ if(!get_config_string (lookup_config (config_tree, "BindToInterface"), &iface))
+ return true;
+
+#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
+ ifr.ifr_ifrn.ifrn_name[IFNAMSIZ - 1] = 0;
+
+ status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr));
+ if(status) {
+ logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface,
+ strerror(errno));
+ return false;
+ }
+#else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */
+ logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface");
+#endif
+
+ return true;
+} /* }}} bool bind_to_interface */
+
+static bool bind_to_address(connection_t *c) { /* {{{ */
+ char *node;
+ struct addrinfo *ai_list;
+ struct addrinfo *ai_ptr;
+ struct addrinfo ai_hints;
+ int status;
+
+ assert(c != NULL);
+ assert(c->socket >= 0);
+
+ node = NULL;
+ if(!get_config_string(lookup_config(config_tree, "BindToAddress"),
+ &node))
+ return true;
+
+ assert(node != NULL);
+
+ memset(&ai_hints, 0, sizeof(ai_hints));
+ ai_hints.ai_family = c->address.sa.sa_family;
+ /* We're called from `do_outgoing_connection' only. */
+ ai_hints.ai_socktype = SOCK_STREAM;
+ ai_hints.ai_protocol = IPPROTO_TCP;
+
+ ai_list = NULL;
+
+ status = getaddrinfo(node, /* service = */ NULL,
+ &ai_hints, &ai_list);
+ if(status) {
+ free(node);
+ logger(LOG_WARNING, _("Error looking up %s port %s: %s"),
+ node, _("any"), gai_strerror(status));
+ return false;
+ }
+ assert(ai_list != NULL);
+
+ status = -1;
+ for(ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) {
+ status = bind(c->socket,
+ ai_list->ai_addr, ai_list->ai_addrlen);
+ if(!status)
+ break;
+ }
+
+
+ if(status) {
+ logger(LOG_ERR, _("Can't bind to %s/tcp: %s"), node,
+ strerror(errno));
+ } else ifdebug(CONNECTIONS) {
+ logger(LOG_DEBUG, "Successfully bound outgoing "
+ "TCP socket to %s", node);
+ }
+
+ free(node);
+ freeaddrinfo(ai_list);
+
+ return status ? false : true;
+} /* }}} bool bind_to_address */
+
+int setup_listen_socket(const sockaddr_t *sa)
+{
int nfd;
char *addrstr;
int option;
return -1;
}
#else
- logger(LOG_WARNING, _("BindToInterface not supported on this platform"));
+ logger(LOG_WARNING, _("%s not supported on this platform"), "BindToInterface");
#endif
}
}
#endif
-#if defined(SOL_SOCKET) && defined(SO_BINDTODEVICE)
- {
- char *iface;
- struct ifreq ifr;
-
- if(get_config_string(lookup_config(config_tree, "BindToInterface"), &iface)) {
- memset(&ifr, 0, sizeof ifr);
- strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
-
- if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr)) {
- closesocket(nfd);
- logger(LOG_ERR, _("Can't bind to interface %s: %s"), iface,
- strerror(errno));
- return -1;
- }
- }
+ if (!bind_to_interface(nfd)) {
+ closesocket(nfd);
+ return -1;
}
-#endif
if(bind(nfd, &sa->sa, SALEN(sa->sa))) {
closesocket(nfd);
}
return nfd;
-}
+} /* int setup_vpn_in_socket */
static void retry_outgoing_handler(int fd, short events, void *data) {
setup_outgoing_connection(data);
setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, &option, sizeof option);
#endif
+ bind_to_interface(c->socket);
+ bind_to_address(c);
+
/* Optimize TCP settings */
configure_tcp(c);
return str;
}
-int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) {
+int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b)
+{
+ int result;
+
+ cp();
+
+ result = a->sa.sa_family - b->sa.sa_family;
+
+ if(result)
+ return result;
+
+ switch (a->sa.sa_family) {
+ case AF_UNSPEC:
+ return 0;
+
+ case AF_UNKNOWN:
+ return strcmp(a->unknown.address, b->unknown.address);
+
+ case AF_INET:
+ return memcmp(&a->in.sin_addr, &b->in.sin_addr, sizeof(a->in.sin_addr));
+
+ case AF_INET6:
+ return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr));
+
+ default:
+ logger(LOG_ERR, _("sockaddrcmp() was called with unknown address family %d, exitting!"),
+ a->sa.sa_family);
+ cp_trace();
+ raise(SIGFPE);
+ exit(0);
+ }
+}
+
+int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b)
+{
int result;
cp();
extern void sockaddr2str(const sockaddr_t *, char **, char **);
extern char *sockaddr2hostname(const sockaddr_t *);
extern int sockaddrcmp(const sockaddr_t *, const sockaddr_t *);
+extern int sockaddrcmp_noport(const sockaddr_t *, const sockaddr_t *);
extern void sockaddrunmap(sockaddr_t *);
extern void sockaddrfree(sockaddr_t *);
extern void sockaddrcpy(sockaddr_t *, const sockaddr_t *);
sockaddrfree(&n->address);
- cipher_close(&n->cipher);
- digest_close(&n->digest);
+ cipher_close(&n->incipher);
+ digest_close(&n->indigest);
+ cipher_close(&n->outcipher);
+ digest_close(&n->outdigest);
event_del(&n->mtuevent);
}
splay_delete(node_tree, n);
+ splay_delete(node_udp_tree, n);
}
node_t *lookup_node(char *name) {
return splay_search(node_udp_tree, &n);
}
+void update_node_udp(node_t *n, const sockaddr_t *sa)
+{
+ splay_delete(node_udp_tree, n);
+
+ if(n->hostname)
+ free(n->hostname);
+
+ if(sa) {
+ n->address = *sa;
+ n->hostname = sockaddr2hostname(&n->address);
+ splay_delete(node_udp_tree, n);
+ splay_insert(node_udp_tree, n);
+ logger(LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname);
+ } else {
+ memset(&n->address, 0, sizeof n->address);
+ logger(LOG_DEBUG, "UDP address of %s cleared", n->name);
+ }
+}
+
int dump_nodes(struct evbuffer *out) {
splay_node_t *node;
node_t *n;
for(node = node_tree->head; node; node = node->next) {
n = node->data;
if(evbuffer_add_printf(out, _(" %s at %s cipher %d digest %d maclength %d compression %d options %lx status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)\n"),
- n->name, n->hostname, cipher_get_nid(&n->cipher),
- digest_get_nid(&n->digest), n->maclength, n->compression,
+ n->name, n->hostname, cipher_get_nid(&n->outcipher),
+ digest_get_nid(&n->outdigest), n->outmaclength, n->outcompression,
n->options, *(uint32_t *)&n->status, n->nexthop ? n->nexthop->name : "-",
n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu) == -1)
return errno;
node_status_t status;
- cipher_t cipher; /* Cipher for UDP packets */
- digest_t digest; /* Digest for UDP packets */
- int maclength; /* Portion of digest to use */
+ cipher_t incipher; /* Cipher for UDP packets */
+ digest_t indigest; /* Digest for UDP packets */
+ int inmaclength; /* Portion of digest to use */
- int compression; /* Compressionlevel, 0 = no compression */
+ cipher_t outcipher; /* Cipher for UDP packets */
+ digest_t outdigest; /* Digest for UDP packets */
+ int outmaclength; /* Portion of digest to use */
+
+ int incompression; /* Compressionlevel, 0 = no compression */
+ int outcompression; /* Compressionlevel, 0 = no compression */
int distance;
struct node_t *nexthop; /* nearest node from us to him */
extern node_t *lookup_node(char *);
extern node_t *lookup_node_udp(const sockaddr_t *);
extern int dump_nodes(struct evbuffer *);
+extern void update_node_udp(node_t *, const sockaddr_t *);
#endif /* __TINC_NODE_H__ */
void digest_close(digest_t *digest) {
}
-bool digest_create(digest_t *digest, void *indata, size_t inlen, void *outdata) {
+bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) {
EVP_MD_CTX ctx;
if(EVP_DigestInit(&ctx, digest->digest)
return false;
}
-bool digest_verify(digest_t *digest, void *indata, size_t inlen, void *cmpdata) {
+bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *cmpdata) {
size_t len = EVP_MD_size(digest->digest);
char outdata[len];
extern bool digest_open_by_nid(struct digest *, int);
extern bool digest_open_sha1(struct digest *);
extern void digest_close(struct digest *);
-extern bool digest_create(struct digest *, void *indata, size_t inlen, void *outdata);
-extern bool digest_verify(struct digest *, void *indata, size_t inlen, void *digestdata);
+extern bool digest_create(struct digest *, const void *indata, size_t inlen, void *outdata);
+extern bool digest_verify(struct digest *, const void *indata, size_t inlen, const void *digestdata);
extern int digest_get_nid(const struct digest *);
extern size_t digest_length(const struct digest *);
extern bool digest_active(const struct digest *);
extern bool send_del_subnet(struct connection_t *, const struct subnet_t *);
extern bool send_add_edge(struct connection_t *, const struct edge_t *);
extern bool send_del_edge(struct connection_t *, const struct edge_t *);
-extern bool send_key_changed(struct connection_t *, const struct node_t *);
-extern bool send_req_key(struct connection_t *, const struct node_t *, const struct node_t *);
-extern bool send_ans_key(struct connection_t *, const struct node_t *, const struct node_t *);
+extern bool send_key_changed();
+extern bool send_req_key(struct node_t *);
+extern bool send_ans_key(struct node_t *);
extern bool send_tcppacket(struct connection_t *, struct vpn_packet_t *);
/* Request handlers */
cp();
if(!c->hischallenge)
- c->hischallenge = xmalloc(len);
+ c->hischallenge = xrealloc(c->hischallenge, len);
/* Copy random data to the buffer */
/* Lookup nodes */
from = lookup_node(from_name);
+ to = lookup_node(to_name);
+
+ if(tunnelserver &&
+ from != myself && from != c->node &&
+ to != myself && to != c->node) {
+ /* ignore indirect edge registrations for tunnelserver */
+ ifdebug(PROTOCOL) logger(LOG_WARNING,
+ _("Ignoring indirect %s from %s (%s)"),
+ "ADD_EDGE", c->name, c->hostname);
+ return true;
+ }
if(!from) {
from = new_node();
node_add(from);
}
- to = lookup_node(to_name);
-
if(!to) {
to = new_node();
to->name = xstrdup(to_name);
node_add(to);
}
- if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node)
- return false;
/* Convert addresses */
/* Lookup nodes */
from = lookup_node(from_name);
+ to = lookup_node(to_name);
+
+ if(tunnelserver &&
+ from != myself && from != c->node &&
+ to != myself && to != c->node) {
+ /* ignore indirect edge registrations for tunnelserver */
+ ifdebug(PROTOCOL) logger(LOG_WARNING,
+ _("Ignoring indirect %s from %s (%s)"),
+ "DEL_EDGE", c->name, c->hostname);
+ return true;
+ }
if(!from) {
ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"),
return true;
}
- to = lookup_node(to_name);
-
if(!to) {
ifdebug(PROTOCOL) logger(LOG_ERR, _("Got %s from %s (%s) which does not appear in the edge tree"),
"DEL_EDGE", c->name, c->hostname);
return true;
}
- if(tunnelserver && from != myself && from != c->node && to != myself && to != c->node)
- return false;
-
/* Check if edge exists */
e = lookup_edge(from, to);
#include "splay_tree.h"
#include "cipher.h"
#include "connection.h"
+#include "crypto.h"
#include "logger.h"
#include "net.h"
#include "netutl.h"
static bool mykeyused = false;
-bool send_key_changed(connection_t *c, const node_t *n) {
+bool send_key_changed() {
cp();
/* Only send this message if some other daemon requested our key previously.
This reduces unnecessary key_changed broadcasts.
*/
- if(n == myself && !mykeyused)
+ if(!mykeyused)
return true;
- return send_request(c, "%d %lx %s", KEY_CHANGED, random(), n->name);
+ return send_request(broadcast, "%d %lx %s", KEY_CHANGED, random(), myself->name);
}
bool key_changed_h(connection_t *c, char *request) {
return true;
}
-bool send_req_key(connection_t *c, const node_t *from, const node_t *to) {
+bool send_req_key(node_t *to) {
cp();
- return send_request(c, "%d %s %s", REQ_KEY, from->name, to->name);
+ return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name);
}
bool req_key_h(connection_t *c, char *request) {
/* Check if this key request is for us */
if(to == myself) { /* Yes, send our own key back */
- mykeyused = true;
- from->received_seqno = 0;
- memset(from->late, 0, sizeof from->late);
- send_ans_key(c, myself, from);
+
+ send_ans_key(from);
} else {
if(tunnelserver)
return false;
return true;
}
- send_req_key(to->nexthop->connection, from, to);
+ send_request(to->nexthop->connection, "%s", request);
}
return true;
}
-bool send_ans_key(connection_t *c, const node_t *from, const node_t *to) {
- size_t keylen = cipher_keylength(&from->cipher);
+bool send_ans_key(node_t *to) {
+ size_t keylen = cipher_keylength(&myself->incipher);
char key[keylen * 2 + 1];
cp();
- cipher_get_key(&from->cipher, key);
+ cipher_open_by_nid(&to->incipher, cipher_get_nid(&myself->incipher));
+ digest_open_by_nid(&to->indigest, digest_get_nid(&myself->indigest));
+ to->inmaclength = myself->inmaclength;
+ to->incompression = myself->incompression;
+
+ randomize(key, keylen);
+ cipher_set_key(&to->incipher, key, true);
+
bin2hex(key, key, keylen);
key[keylen * 2] = '\0';
- return send_request(c, "%d %s %s %s %d %d %d %d", ANS_KEY,
- from->name, to->name, key,
- cipher_get_nid(&from->cipher),
- digest_get_nid(&from->digest), from->maclength,
- from->compression);
+ // Reset sequence number and late packet window
+ mykeyused = true;
+ to->received_seqno = 0;
+ memset(to->late, 0, sizeof(to->late));
+
+ 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), to->inmaclength,
+ to->incompression);
}
bool ans_key_h(connection_t *c, char *request) {
/* Check and lookup cipher and digest algorithms */
- if(!cipher_open_by_nid(&from->cipher, cipher)) {
+ if(!cipher_open_by_nid(&from->outcipher, cipher)) {
logger(LOG_ERR, _("Node %s (%s) uses unknown cipher!"), from->name, from->hostname);
return false;
}
- if(strlen(key) / 2 != cipher_keylength(&from->cipher)) {
+ if(strlen(key) / 2 != cipher_keylength(&from->outcipher)) {
logger(LOG_ERR, _("Node %s (%s) uses wrong keylength!"), from->name, from->hostname);
return false;
}
- from->maclength = maclength;
+ from->outmaclength = maclength;
- if(!digest_open_by_nid(&from->digest, digest)) {
+ if(!digest_open_by_nid(&from->outdigest, digest)) {
logger(LOG_ERR, _("Node %s (%s) uses unknown digest!"), from->name, from->hostname);
return false;
}
- if(from->maclength > digest_length(&from->digest) || from->maclength < 0) {
+ if(from->outmaclength > digest_length(&from->outdigest) || from->outmaclength < 0) {
logger(LOG_ERR, _("Node %s (%s) uses bogus MAC length!"), from->name, from->hostname);
return false;
}
return false;
}
- from->compression = compression;
+ from->outcompression = compression;
/* Update our copy of the origin's packet key */
- hex2bin(key, key, cipher_keylength(&from->cipher));
- cipher_set_key(&from->cipher, key, false);
+ hex2bin(key, key, cipher_keylength(&from->outcipher));
+ cipher_set_key(&from->outcipher, key, false);
from->status.validkey = true;
from->status.waitingforkey = false;
return false;
}
- /* Check if owner name is a valid */
+ /* Check if owner name is valid */
if(!check_id(name)) {
logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "ADD_SUBNET", c->name,
free_subnet(allowed);
}
- if(!cfg)
+ if(!cfg) {
+ logger(LOG_WARNING, _("Unauthorized %s from %s (%s) for %s"),
+ "ADD_SUBNET", c->name, c->hostname, subnetstr);
return false;
+ }
free_subnet(allowed);
}
return false;
}
- /* Check if owner name is a valid */
+ /* Check if owner name is valid */
if(!check_id(name)) {
logger(LOG_ERR, _("Got bad %s from %s (%s): %s"), "DEL_SUBNET", c->name,
return false;
}
- /* Check if the owner of the new subnet is in the connection list */
-
- owner = lookup_node(name);
-
- if(!owner) {
- ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"),
- "DEL_SUBNET", c->name, c->hostname, name);
- return true;
- }
-
- if(tunnelserver && owner != myself && owner != c->node)
- return false;
-
/* Check if subnet string is valid */
if(!str2net(&s, subnetstr)) {
if(seen_request(request))
return true;
+ /* Check if the owner of the subnet being deleted is in the connection list */
+
+ owner = lookup_node(name);
+
+ if(tunnelserver && owner != myself && owner != c->node) {
+ /* in case of tunnelserver, ignore indirect subnet deletion */
+ ifdebug(PROTOCOL) logger(LOG_WARNING, _("Ignoring indirect %s from %s (%s) for %s"),
+ "DEL_SUBNET", c->name, c->hostname, subnetstr);
+ return true;
+ }
+
+ if(!owner) {
+ ifdebug(PROTOCOL) logger(LOG_WARNING, _("Got %s from %s (%s) for %s which is not in our node tree"),
+ "DEL_SUBNET", c->name, c->hostname, name);
+ return true;
+ }
+
/* If everything is correct, delete the subnet from the list of the owner */
s.owner = owner;
#include LZO1X_H
+#ifndef HAVE_MINGW
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
+#endif
+
#include <getopt.h>
#include "conf.h"
/* If nonzero, disable swapping for this process. */
bool do_mlock = false;
+/* If nonzero, chroot to netdir after startup. */
+static bool do_chroot = false;
+
+/* If !NULL, do setuid to given user after startup */
+static const char *switchuser = NULL;
+
/* If nonzero, write log entries to a separate file. */
bool use_logfile = false;
{"debug", optional_argument, NULL, 'd'},
{"bypass-security", no_argument, NULL, 3},
{"mlock", no_argument, NULL, 'L'},
+ {"chroot", no_argument, NULL, 'R'},
+ {"user", required_argument, NULL, 'U'},
{"logfile", optional_argument, NULL, 4},
{"controlsocket", required_argument, NULL, 5},
{NULL, 0, NULL, 0}
" --logfile[=FILENAME] Write log entries to a logfile.\n"
" --controlsocket=FILENAME Open control socket at FILENAME.\n"
" --bypass-security Disables meta protocol security, for debugging.\n"
- " --help Display this help and exit.\n"
+ " -R, --chroot chroot to NET dir at startup.\n"
+ " -U, --user=USER setuid to given USER at startup.\n" " --help Display this help and exit.\n"
" --version Output version information and exit.\n\n"));
printf(_("Report bugs to tinc@tinc-vpn.org.\n"));
}
int r;
int option_index = 0;
- while((r = getopt_long(argc, argv, "c:DLd::n:", long_options, &option_index)) != EOF) {
+ while((r = getopt_long(argc, argv, "c:DLd::n:RU:", long_options, &option_index)) != EOF) {
switch (r) {
case 0: /* long option */
break;
break;
case 'L': /* no detach */
+#ifndef HAVE_MLOCKALL
+ logger(LOG_ERR, _("%s not supported on this platform"), "mlockall()");
+ return false;
+#else
do_mlock = true;
break;
+#endif
case 'd': /* inc debug level */
if(optarg)
netname = xstrdup(optarg);
break;
+ case 'R': /* chroot to NETNAME dir */
+ do_chroot = true;
+ break;
+
+ case 'U': /* setuid to USER */
+ switchuser = optarg;
+ break;
+
case 1: /* show help */
show_help = true;
break;
if (confbase) free(confbase);
}
+static bool drop_privs() {
+#ifdef HAVE_MINGW
+ if (switchuser) {
+ logger(LOG_ERR, _("%s not supported on this platform"), "-U");
+ return false;
+ }
+ if (do_chroot) {
+ logger(LOG_ERR, _("%s not supported on this platform"), "-R");
+ return false;
+ }
+#else
+ uid_t uid = 0;
+ if (switchuser) {
+ struct passwd *pw = getpwnam(switchuser);
+ if (!pw) {
+ logger(LOG_ERR, _("unknown user `%s'"), switchuser);
+ return false;
+ }
+ uid = pw->pw_uid;
+ if (initgroups(switchuser, pw->pw_gid) != 0 ||
+ setgid(pw->pw_gid) != 0) {
+ logger(LOG_ERR, _("System call `%s' failed: %s"),
+ "initgroups", strerror(errno));
+ return false;
+ }
+ endgrent();
+ endpwent();
+ }
+ if (do_chroot) {
+ tzset(); /* for proper timestamps in logs */
+ if (chroot(confbase) != 0 || chdir("/") != 0) {
+ logger(LOG_ERR, _("System call `%s' failed: %s"),
+ "chroot", strerror(errno));
+ return false;
+ }
+ free(confbase);
+ confbase = xstrdup("");
+ }
+ if (switchuser)
+ if (setuid(uid) != 0) {
+ logger(LOG_ERR, _("System call `%s' failed: %s"),
+ "setuid", strerror(errno));
+ return false;
+ }
+#endif
+ return true;
+}
+
+#ifdef HAVE_MINGW
+# define setpriority(level) SetPriorityClass(GetCurrentProcess(), level);
+#else
+# define NORMAL_PRIORITY_CLASS 0
+# define BELOW_NORMAL_PRIORITY_CLASS 10
+# define HIGH_PRIORITY_CLASS -10
+# define setpriority(level) nice(level)
+#endif
+
int main(int argc, char **argv)
{
program_name = argv[0];
if(!init_control())
return 1;
- /* Lock all pages into memory if requested */
-
- if(do_mlock)
-#ifdef HAVE_MLOCKALL
- if(mlockall(MCL_CURRENT | MCL_FUTURE)) {
- logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall",
- strerror(errno));
-#else
- {
- logger(LOG_ERR, _("mlockall() not supported on this platform!"));
-#endif
- return -1;
- }
-
g_argv = argv;
init_configuration(&config_tree);
if(!detach())
return 1;
-
+
+#ifdef HAVE_MLOCKALL
+ /* Lock all pages into memory if requested.
+ * This has to be done after daemon()/fork() so it works for child.
+ * No need to do that in parent as it's very short-lived. */
+ if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
+ logger(LOG_ERR, _("System call `%s' failed: %s"), "mlockall",
+ strerror(errno));
+ return 1;
+ }
+#endif
/* Setup sockets and open device. */
- if(!setup_network_connections())
+ if(!setup_network())
+ goto end;
+
+ /* Initiate all outgoing connections. */
+
+ try_outgoing_connections();
+
+ /* Change process priority */
+
+ char *priority = 0;
+
+ if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) {
+ if(!strcasecmp(priority, "Normal"))
+ setpriority(NORMAL_PRIORITY_CLASS);
+ else if(!strcasecmp(priority, "Low"))
+ setpriority(BELOW_NORMAL_PRIORITY_CLASS);
+ else if(!strcasecmp(priority, "High"))
+ setpriority(HIGH_PRIORITY_CLASS);
+ else {
+ logger(LOG_ERR, _("Invalid priority `%s`!"), priority);
+ goto end;
+ }
+ }
+
+ /* drop privileges */
+ if (!drop_privs())
goto end;
/* Start main loop. It only exits when tinc is killed. */
crypto_exit();
+ exit_configuration(&config_tree);
+ free_names();
+
return status;
}