+Version 1.1pre2 Juli 17 2011
+
+ * .cookie files are renamed to .pid files, which are compatible with 1.0.x.
+
+ * Experimental protocol enhancements that can be enabled with the option
+ ExperimentalProtocol = yes:
+
+ * Ephemeral ECDH key exchange will be used for both the meta protocol and
+ UDP session keys.
+ * Key exchanges are signed with ECDSA.
+ * ECDSA public keys are automatically exchanged after RSA authentication if
+ nodes do not know each other's ECDSA public key yet.
+
+Version 1.1pre1 June 25 2011
+
+ * Control interface allows control of a running tinc daemon. Used by:
+ * tincctl, a commandline utility
+ * tinc-gui, a preliminary GUI implemented in Python/wxWidgets
+
+ * Code cleanups and reorganization.
+
+ * Repleacable cryptography backend, currently supports OpenSSL and libgcrypt.
+
+ * Use libevent to handle I/O events and timeouts.
+
+ * Use splay trees instead of AVL trees to manage internal datastructures.
+
+ Thanks to Scott Lamb and Sven-Haegar Koch for their contributions to this
+ version of tinc.
+
+ Version 1.0.18 March 25 2012
+
+ * Fixed IPv6 in switch mode by turning off DecrementTTL by default.
+
+ * Allow a port number to be specified in BindToAddress, which also allows tinc
+ to listen on multiple ports.
+
+ * Add support for multicast communication with UML/QEMU/KVM.
+
+ Version 1.0.17 March 10 2012
+
+ * The DeviceType option can now be used to select dummy, raw socket, UML and
+ VDE devices without needing to recompile tinc.
+
+ * Allow multiple BindToAddress statements.
+
+ * Decrement TTL value of IPv4 and IPv6 packets.
+
+ * Add LocalDiscovery option allowing tinc to detect peers that are behind the
+ same NAT.
+
+ * Accept Subnets passed with the -o option when StrictSubnets = yes.
+
+ * Disabling old RSA keys when generating new ones now also works properly on
+ Windows.
+
Version 1.0.16 July 23 2011
* Fixed a performance issue with TCP communication under Windows.
-This is the README file for tinc version 1.0.18. Installation
+This is the README file for tinc version 1.1pre2. Installation
instructions may be found in the INSTALL file.
- tinc is Copyright (C) 1998-2011 by:
+ tinc is Copyright (C) 1998-2012 by:
Ivo Timmermans,
Guus Sliepen <guus@tinc-vpn.org>,
## Produce this file with automake to get Makefile.in
-sbin_PROGRAMS = tincd
+sbin_PROGRAMS = tincd tincctl sptps_test
-EXTRA_DIST = linux/device.c bsd/device.c solaris/device.c cygwin/device.c mingw/device.c mingw/common.h
+EXTRA_DIST = linux bsd solaris cygwin mingw openssl gcrypt
-tincd_SOURCES = conf.c connection.c edge.c event.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \
- net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \
- protocol_key.c protocol_subnet.c route.c subnet.c tincd.c \
+tincd_SOURCES = \
+ utils.c getopt.c getopt1.c list.c splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c \
+ buffer.c conf.c connection.c control.c edge.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \
+ net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \
+ protocol_key.c protocol_subnet.c route.c sptps.c subnet.c tincd.c \
- dummy_device.c raw_socket_device.c
+ dummy_device.c raw_socket_device.c multicast_device.c
if UML
tincd_SOURCES += uml_device.c
return x;
}
-static void disable_old_keys(const char *filename) {
- char tmpfile[PATH_MAX] = "";
- char buf[1024];
- bool disabled = false;
- FILE *r, *w;
-
- r = fopen(filename, "r");
- if(!r)
- return;
-
- snprintf(tmpfile, sizeof tmpfile, "%s.tmp", filename);
-
- w = fopen(tmpfile, "w");
-
- while(fgets(buf, sizeof buf, r)) {
- if(!strncmp(buf, "-----BEGIN RSA", 14)) {
- buf[11] = 'O';
- buf[12] = 'L';
- buf[13] = 'D';
- disabled = true;
- }
- else if(!strncmp(buf, "-----END RSA", 12)) {
- buf[ 9] = 'O';
- buf[10] = 'L';
- buf[11] = 'D';
- disabled = true;
- }
- if(w && fputs(buf, w) < 0) {
- disabled = false;
- break;
- }
- }
-
- if(w)
- fclose(w);
- fclose(r);
-
- if(!w && disabled) {
- fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
- return;
- }
-
- if(disabled) {
-#ifdef HAVE_MINGW
- // We cannot atomically replace files on Windows.
- char bakfile[PATH_MAX] = "";
- snprintf(bakfile, sizeof bakfile, "%s.bak", filename);
- if(rename(filename, bakfile) || rename(tmpfile, filename)) {
- rename(bakfile, filename);
-#else
- if(rename(tmpfile, filename)) {
-#endif
- fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n");
- } else {
-#ifdef HAVE_MINGW
- unlink(bakfile);
-#endif
- fprintf(stderr, "Warning: old key(s) found and disabled.\n");
- }
- }
+bool append_config_file(const char *name, const char *key, const char *value) {
+ char *fname;
+ xasprintf(&fname, "%s/hosts/%s", confbase, name);
- unlink(tmpfile);
-}
+ FILE *fp = fopen(fname, "a");
-FILE *ask_and_open(const char *filename, const char *what) {
- FILE *r;
- char *directory;
- char line[PATH_MAX];
- const char *fn;
-
- /* Check stdin and stdout */
- if(!isatty(0) || !isatty(1)) {
- /* Argh, they are running us from a script or something. Write
- the files to the current directory and let them burn in hell
- for ever. */
- fn = filename;
+ if(!fp) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno));
} else {
- /* Ask for a file and/or directory name. */
- fprintf(stdout, "Please enter a file to save %s to [%s]: ",
- what, filename);
- fflush(stdout);
-
- fn = readline(stdin, line, sizeof line);
-
- if(!fn) {
- fprintf(stderr, "Error while reading stdin: %s\n",
- strerror(errno));
- return NULL;
- }
-
- if(!strlen(fn))
- /* User just pressed enter. */
- fn = filename;
- }
-
-#ifdef HAVE_MINGW
- if(fn[0] != '\\' && fn[0] != '/' && !strchr(fn, ':')) {
-#else
- if(fn[0] != '/') {
-#endif
- /* The directory is a relative path or a filename. */
- char *p;
-
- directory = get_current_dir_name();
- xasprintf(&p, "%s/%s", directory, fn);
- free(directory);
- fn = p;
+ fprintf(fp, "\n# The following line was automatically added by tinc\n%s = %s\n", key, value);
+ fclose(fp);
}
- umask(0077); /* Disallow everything for group and other */
+
- disable_old_keys(fn);
-
- /* Open it first to keep the inode busy */
-
- r = fopen(fn, "a");
-
- if(!r) {
- fprintf(stderr, "Error opening file `%s': %s\n",
- fn, strerror(errno));
- return NULL;
- }
+ free(fname);
- return r;
+ return fp;
}
-
- bool disable_old_keys(FILE *f) {
- char buf[100];
- long pos;
- bool disabled = false;
-
- rewind(f);
- pos = ftell(f);
-
- if(pos < 0)
- return false;
--
- while(fgets(buf, sizeof buf, f)) {
- if(!strncmp(buf, "-----BEGIN RSA", 14)) {
- buf[11] = 'O';
- buf[12] = 'L';
- buf[13] = 'D';
- if(fseek(f, pos, SEEK_SET))
- break;
- if(fputs(buf, f) <= 0)
- break;
- disabled = true;
- }
- else if(!strncmp(buf, "-----END RSA", 12)) {
- buf[ 9] = 'O';
- buf[10] = 'L';
- buf[11] = 'D';
- if(fseek(f, pos, SEEK_SET))
- break;
- if(fputs(buf, f) <= 0)
- break;
- disabled = true;
- }
- pos = ftell(f);
- if(pos < 0)
- break;
- }
--
- return disabled;
- }
extern bool get_config_subnet(const config_t *, struct subnet_t **);
extern config_t *parse_config_line(char *, const char *, int);
-extern bool read_config_file(avl_tree_t *, const char *);
-extern void read_config_options(avl_tree_t *, const char *);
+extern bool read_config_file(splay_tree_t *, const char *);
+extern void read_config_options(splay_tree_t *, const char *);
extern bool read_server_config(void);
extern bool read_connection_config(struct connection_t *);
-extern FILE *ask_and_open(const char *, const char *);
-extern bool is_safe_path(const char *);
+extern bool append_config_file(const char *, const char *, const char *);
- extern bool disable_old_keys(FILE *);
#endif /* __TINC_CONF_H__ */
}
connection_t *new_connection(void) {
- connection_t *c;
+ return xmalloc_and_zero(sizeof(connection_t));
+}
- void free_connection(connection_t *c) {
- if(!c)
- return;
-
- if(c->name)
- free(c->name);
-
- if(c->hostname)
- free(c->hostname);
-
- c = xmalloc_and_zero(sizeof(connection_t));
++void free_connection_partially(connection_t *c) {
+ cipher_close(&c->incipher);
+ digest_close(&c->indigest);
+ cipher_close(&c->outcipher);
+ digest_close(&c->outdigest);
- if(!c)
- return NULL;
+ sptps_stop(&c->sptps);
+ ecdsa_free(&c->ecdsa);
+ rsa_free(&c->rsa);
- gettimeofday(&c->start, NULL);
+ if(c->hischallenge)
+ free(c->hischallenge);
- if(c->config_tree)
- exit_configuration(&c->config_tree);
-
- return c;
-}
+ buffer_clear(&c->inbuf);
+ buffer_clear(&c->outbuf);
+
+ if(event_initialized(&c->inevent))
+ event_del(&c->inevent);
-void free_connection_partially(connection_t *c) {
- free(c->inkey);
- free(c->outkey);
- free(c->mychallenge);
- free(c->hischallenge);
- free(c->outbuf);
-
- c->inkey = NULL;
- c->outkey = NULL;
- c->mychallenge = NULL;
- c->hischallenge = NULL;
- c->outbuf = NULL;
-
- c->buflen = 0;
- c->reqlen = 0;
- c->tcplen = 0;
- c->allow_request = 0;
- c->outbuflen = 0;
- c->outbufsize = 0;
- c->outbufstart = 0;
-
- if(c->inctx) {
- EVP_CIPHER_CTX_cleanup(c->inctx);
- free(c->inctx);
- c->inctx = NULL;
- }
+ if(event_initialized(&c->outevent))
+ event_del(&c->outevent);
- if(c->outctx) {
- EVP_CIPHER_CTX_cleanup(c->outctx);
- free(c->outctx);
- c->outctx = NULL;
- }
+ if(c->socket > 0)
+ closesocket(c->socket);
- if(c->rsa_key) {
- RSA_free(c->rsa_key);
- c->rsa_key = NULL;
- }
++ c->socket = -1;
+ }
+
+ void free_connection(connection_t *c) {
++ if(!c)
++ return;
++
+ free_connection_partially(c);
+
+ free(c->name);
+ free(c->hostname);
+
+ if(c->config_tree)
+ exit_configuration(&c->config_tree);
+
free(c);
}
extern void exit_connections(void);
extern connection_t *new_connection(void) __attribute__ ((__malloc__));
extern void free_connection(connection_t *);
+ extern void free_connection_partially(connection_t *);
extern void connection_add(connection_t *);
extern void connection_del(connection_t *);
-extern void dump_connections(void);
+extern bool dump_connections(struct connection_t *);
#endif /* __TINC_CONNECTION_H__ */
--- /dev/null
- logger(LOG_ERR, "Device variable required for %s", device_info);
+ /*
+ device.c -- multicast socket
+ Copyright (C) 2002-2005 Ivo Timmermans,
+ 2002-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.
+ */
+
+ #include "system.h"
+
+ #include "conf.h"
+ #include "device.h"
+ #include "net.h"
+ #include "logger.h"
+ #include "netutl.h"
+ #include "utils.h"
+ #include "route.h"
+ #include "xalloc.h"
+
+ static char *device_info;
+
+ static uint64_t device_total_in = 0;
+ static uint64_t device_total_out = 0;
+
+ static struct addrinfo *ai = NULL;
+ static mac_t ignore_src = {{0}};
+
+ static bool setup_device(void) {
+ char *host;
+ char *port;
+ char *space;
+ int ttl = 1;
+
+ device_info = "multicast socket";
+
+ get_config_string(lookup_config(config_tree, "Interface"), &iface);
+
+ if(!get_config_string(lookup_config(config_tree, "Device"), &device)) {
- logger(LOG_ERR, "Port number required for %s", device_info);
++ logger(DEBUG_ALWAYS, LOG_ERR, "Device variable required for %s", device_info);
+ return false;
+ }
+
+ host = xstrdup(device);
+ space = strchr(host, ' ');
+ if(!space) {
- logger(LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno));
++ logger(DEBUG_ALWAYS, LOG_ERR, "Port number required for %s", device_info);
+ return false;
+ }
+
+ *space++ = 0;
+ port = space;
+ space = strchr(port, ' ');
+
+ if(space) {
+ *space++ = 0;
+ ttl = atoi(space);
+ }
+
+ ai = str2addrinfo(host, port, SOCK_DGRAM);
+ if(!ai)
+ return false;
+
+ device_fd = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP);
+ if(device_fd < 0) {
- logger(LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno));
++ logger(DEBUG_ALWAYS, LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno));
+ return false;
+ }
+
+ #ifdef FD_CLOEXEC
+ fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
+
+ static const int one = 1;
+ setsockopt(device_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one);
+
+ if(bind(device_fd, ai->ai_addr, ai->ai_addrlen)) {
+ closesocket(device_fd);
- logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
++ logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno));
+ return false;
+ }
+
+ switch(ai->ai_family) {
+ #ifdef IP_ADD_MEMBERSHIP
+ case AF_INET: {
+ struct ip_mreq mreq;
+ struct sockaddr_in in;
+ memcpy(&in, ai->ai_addr, sizeof in);
+ mreq.imr_multiaddr.s_addr = in.sin_addr.s_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ if(setsockopt(device_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof mreq)) {
- logger(LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
++ logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
+ closesocket(device_fd);
+ return false;
+ }
+ #ifdef IP_MULTICAST_LOOP
+ setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void *)&one, sizeof one);
+ #endif
+ #ifdef IP_MULTICAST_TTL
+ setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl, sizeof ttl);
+ #endif
+ } break;
+ #endif
+
+ #ifdef IPV6_JOIN_GROUP
+ case AF_INET6: {
+ struct ipv6_mreq mreq;
+ struct sockaddr_in6 in6;
+ memcpy(&in6, ai->ai_addr, sizeof in6);
+ memcpy(&mreq.ipv6mr_multiaddr, &in6.sin6_addr, sizeof mreq.ipv6mr_multiaddr);
+ mreq.ipv6mr_interface = in6.sin6_scope_id;
+ if(setsockopt(device_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void *)&mreq, sizeof mreq)) {
- logger(LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family);
++ logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno));
+ closesocket(device_fd);
+ return false;
+ }
+ #ifdef IPV6_MULTICAST_LOOP
+ setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const void *)&one, sizeof one);
+ #endif
+ #ifdef IPV6_MULTICAST_HOPS
+ setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void *)&ttl, sizeof ttl);
+ #endif
+ } break;
+ #endif
+
+ default:
- logger(LOG_INFO, "%s is a %s", device, device_info);
++ logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family);
+ closesocket(device_fd);
+ return false;
+ }
+
- logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
++ logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info);
+
+ return true;
+ }
+
+ static void close_device(void) {
+ close(device_fd);
+
+ free(device);
+ free(iface);
+
+ if(ai)
+ freeaddrinfo(ai);
+ }
+
+ static bool read_packet(vpn_packet_t *packet) {
+ int lenin;
+
+ if((lenin = recv(device_fd, packet->data, MTU, 0)) <= 0) {
- ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info);
++ logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info,
+ device, strerror(errno));
+ return false;
+ }
+
+ if(!memcmp(&ignore_src, packet->data + 6, sizeof ignore_src)) {
- ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
++ logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info);
+ packet->len = 0;
+ return true;
+ }
+
+ packet->len = lenin;
+
+ device_total_in += packet->len;
+
- ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
++ logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len,
+ device_info);
+
+ return true;
+ }
+
+ static bool write_packet(vpn_packet_t *packet) {
- logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device,
++ logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s",
+ packet->len, device_info);
+
+ if(sendto(device_fd, packet->data, packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) {
- logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device);
- logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in);
- logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
++ logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device,
+ strerror(errno));
+ return false;
+ }
+
+ device_total_out += packet->len;
+
+ memcpy(&ignore_src, packet->data + 6, sizeof ignore_src);
+
+ return true;
+ }
+
+ static void dump_device_stats(void) {
- logger(LOG_ERR, "Raw socket device not supported on this platform");
++ logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device);
++ logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in);
++ logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out);
+ }
+
+ const devops_t multicast_devops = {
+ .setup = setup_device,
+ .close = close_device,
+ .read = read_packet,
+ .write = write_packet,
+ .dump_stats = dump_device_stats,
+ };
+
+ #if 0
+
+ static bool not_supported(void) {
++ logger(DEBUG_ALWAYS, LOG_ERR, "Raw socket device not supported on this platform");
+ return false;
+ }
+
+ const devops_t multicast_devops = {
+ .setup = not_supported,
+ .close = NULL,
+ .read = NULL,
+ .write = NULL,
+ .dump_stats = NULL,
+ };
+ #endif
}
}
- /* Check if this was our outgoing connection */
+ free_connection_partially(c);
- if(c->outgoing)
- retry_outgoing(c->outgoing);
+ /* Check if this was our outgoing connection */
- connection_del(c);
+ if(c->outgoing) {
- c->status.remove = false;
+ do_outgoing_connection(c);
+ }
}
/*
if(c->last_ping_time + pingtimeout <= now) {
if(c->status.active) {
if(c->status.pinged) {
- ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
+ logger(DEBUG_CONNECTIONS, LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds",
- c->name, c->hostname, now - c->last_ping_time);
+ c->name, c->hostname, (long)now - c->last_ping_time);
- c->status.timeout = true;
terminate_connection(c, true);
+ continue;
} else if(c->last_ping_time + pinginterval <= now) {
send_ping(c);
}
make_names();
if(show_version) {
- printf("%s version %s (built %s %s, protocol %d)\n", PACKAGE,
- VERSION, __DATE__, __TIME__, PROT_CURRENT);
+ printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE,
+ VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR);
- printf("Copyright (C) 1998-2011 Ivo Timmermans, Guus Sliepen and others.\n"
+ printf("Copyright (C) 1998-2012 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"
}
static bool read_packet(vpn_packet_t *packet) {
- int lenin = plug.vde_recv(conn, packet->data, MTU, 0);
+ int lenin = (ssize_t)plug.vde_recv(conn, packet->data, MTU, 0);
if(lenin <= 0) {
- logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno));
running = false;
return false;
}
}
static bool write_packet(vpn_packet_t *packet) {
- if(plug.vde_send(conn, packet->data, packet->len, 0) < 0) {
+ if((ssize_t)plug.vde_send(conn, packet->data, packet->len, 0) < 0) {
if(errno != EINTR && errno != EAGAIN) {
- logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
+ logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno));
running = false;
}