+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.16 July 23 2011
+
+ * Fixed a performance issue with TCP communication under Windows.
+
+ * Fixed code that, during network outages, would cause tinc to exit when it
+ thought two nodes with identical Names were on the VPN.
+
Version 1.0.15 June 24 2011
* Improved logging to file.
dnl We do this in multiple stages, because unlike Linux all the other operating systems really suck and don't include their own dependencies.
AC_HEADER_STDC
-AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h time.h sys/uio.h sys/wait.h netdb.h arpa/inet.h dirent.h])
-AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h netpacket/packet.h],
+AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h])
- AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h],
++AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h netpacket/packet.h],
[], [], [#include "have.h"]
)
AC_CHECK_HEADERS([netinet/if_ether.h netinet/ip.h netinet/ip6.h],
Specifying . for @var{netname} is the same as not specifying any @var{netname}.
@xref{Multiple networks}.
-@item -K, --generate-keys[=@var{bits}]
-Generate public/private keypair of @var{bits} length. If @var{bits} is not specified,
-2048 is the default. tinc will ask where you want to store the files,
-but will default to the configuration directory (you can use the -c or -n option
-in combination with -K). After that, tinc will quit.
+@item --pidfile=@var{filename}
+Store a cookie in @var{filename} which allows tincctl to authenticate.
+If unspecified, the default is
+@file{@value{localstatedir}/run/tinc.@var{netname}.pid}.
+ @item -o, --option=[@var{HOST}.]@var{KEY}=@var{VALUE}
+ Without specifying a @var{HOST}, this will set server configuration variable @var{KEY} to @var{VALUE}.
+ If specified as @var{HOST}.@var{KEY}=@var{VALUE},
+ this will set the host configuration variable @var{KEY} of the host named @var{HOST} to @var{VALUE}.
+ This option can be used more than once to specify multiple configuration variables.
+
@item -L, --mlock
Lock tinc into main memory.
This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
.Nd tinc VPN daemon
.Sh SYNOPSIS
.Nm
- .Op Fl cdDKnLRU
-.Op Fl cdDkKnoLRU
++.Op Fl cdDKnoLRU
.Op Fl -config Ns = Ns Ar DIR
.Op Fl -no-detach
.Op Fl -debug Ns Op = Ns Ar LEVEL
-.Op Fl -kill Ns Op = Ns Ar SIGNAL
.Op Fl -net Ns = Ns Ar NETNAME
-.Op Fl -generate-keys Ns Op = Ns Ar BITS
+ .Op Fl -option Ns = Ns Ar [HOST.]KEY=VALUE
.Op Fl -mlock
.Op Fl -logfile Ns Op = Ns Ar FILE
-.Op Fl -pidfile Ns = Ns Ar FILE
.Op Fl -bypass-security
.Op Fl -chroot
.Op Fl -user Ns = Ns Ar USER
.Ar NETNAME
is the same as not specifying any
.Ar NETNAME .
-.It Fl K, -generate-keys Ns Op = Ns Ar BITS
-Generate public/private RSA keypair and exit.
-If
-.Ar BITS
-is omitted, the default length will be 2048 bits.
-When saving keys to existing files, tinc will not delete the old keys,
-you have to remove them manually.
+ .It Fl o, -option Ns = Ns Ar [HOST.]KEY=VALUE
+ Without specifying a
+ .Ar HOST ,
+ this will set server configuration variable
+ .Ar KEY
+ to
+ .Ar VALUE .
+ If specified as
+ .Ar HOST.KEY=VALUE ,
+ this will set the host configuration variable
+ .Ar KEY
+ of the host named
+ .Ar HOST
+ to
+ .Ar VALUE .
+ This option can be used more than once to specify multiple configuration variables.
.It Fl L, -mlock
Lock tinc into main memory.
This will prevent sensitive data like shared private keys to be written to the system swap files/partitions.
## Produce this file with automake to get Makefile.in
-sbin_PROGRAMS = tincd
+sbin_PROGRAMS = tincd tincctl sptps_test
- EXTRA_DIST = linux bsd solaris cygwin mingw raw_socket uml_socket openssl gcrypt
-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 \
+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 subnet.c tincd.c
+ protocol_key.c protocol_subnet.c route.c subnet.c tincd.c \
+ dummy_device.c raw_socket_device.c
+
+ if UML
+ tincd_SOURCES += uml_device.c
+ endif
+
+ if VDE
+ tincd_SOURCES += vde_device.c
+ endif
+nodist_tincd_SOURCES = \
+ device.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c rsa.c
+
+tincctl_SOURCES = \
+ utils.c getopt.c getopt1.c dropin.c \
+ list.c tincctl.c top.c
+
+nodist_tincctl_SOURCES = \
+ ecdsagen.c rsagen.c
+
+sptps_test_SOURCES = \
+ logger.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c \
+ sptps.c sptps_test.c
+
if TUNEMU
tincd_SOURCES += bsd/tunemu.c
endif
free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
switch(device_type) {
case DEVICE_TYPE_TUN:
#include "utils.h"
#include "xalloc.h"
-avl_tree_t *connection_tree; /* Meta connections */
+splay_tree_t *connection_tree; /* Meta connections */
- connection_t *broadcast;
+ connection_t *everyone;
static int connection_compare(const connection_t *a, const connection_t *b) {
return a < b ? -1 : a == b ? 0 : 1;
}
void init_connections(void) {
- connection_tree = avl_alloc_tree((avl_compare_t) connection_compare, (avl_action_t) free_connection);
+ connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection);
- broadcast = new_connection();
- broadcast->name = xstrdup("everyone");
- broadcast->hostname = xstrdup("BROADCAST");
+ everyone = new_connection();
+ everyone->name = xstrdup("everyone");
+ everyone->hostname = xstrdup("BROADCAST");
}
void exit_connections(void) {
- avl_delete_tree(connection_tree);
+ splay_delete_tree(connection_tree);
- free_connection(broadcast);
+ free_connection(everyone);
}
connection_t *new_connection(void) {
int tcplen; /* length of incoming TCPpacket */
int allow_request; /* defined if there's only one request possible */
- char *outbuf; /* metadata output buffer */
- int outbufstart; /* index of first meaningful byte in output buffer */
- int outbuflen; /* number of meaningful bytes in output buffer */
- int outbufsize; /* number of bytes allocated to output buffer */
-
time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */
- time_t last_flushed_time; /* last time buffer was empty. Only meaningful if outbuflen > 0 */
- avl_tree_t *config_tree; /* Pointer to configuration tree belonging to him */
+ splay_tree_t *config_tree; /* Pointer to configuration tree belonging to him */
} connection_t;
-extern avl_tree_t *connection_tree;
+extern splay_tree_t *connection_tree;
- extern connection_t *broadcast;
+ extern connection_t *everyone;
extern void init_connections(void);
extern void exit_connections(void);
free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
- if((lenin = read(sp[0], packet->data, MTU)) <= 0) {
+ if((inlen = read(sp[0], packet->data, MTU)) <= 0) {
logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
return false;
return true;
}
- bool write_packet(vpn_packet_t *packet) {
+ static bool write_packet(vpn_packet_t *packet) {
- long lenout;
+ long outlen;
ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
packet->len, device_info);
extern int device_fd;
extern char *device;
-
extern char *iface;
- extern bool setup_device(void);
- extern void close_device(void);
- extern bool read_packet(struct vpn_packet_t *);
- extern bool write_packet(struct vpn_packet_t *);
- extern void dump_device_stats(void);
+extern uint64_t device_in_packets;
+extern uint64_t device_in_bytes;
+extern uint64_t device_out_packets;
+extern uint64_t device_out_bytes;
+
+ typedef struct devops_t {
+ bool (*setup)(void);
+ void (*close)(void);
+ bool (*read)(struct vpn_packet_t *);
+ bool (*write)(struct vpn_packet_t *);
+ void (*dump_stats)(void);
+ } devops_t;
+
+ extern const devops_t os_devops;
+ extern const devops_t dummy_devops;
+ extern const devops_t raw_socket_devops;
+ extern const devops_t uml_devops;
+ extern const devops_t vde_devops;
+ extern devops_t devops;
#endif /* __TINC_DEVICE_H__ */
static char ifrname[IFNAMSIZ];
static char *device_info;
-static uint64_t device_total_in = 0;
-static uint64_t device_total_out = 0;
+uint64_t device_in_packets = 0;
+uint64_t device_in_bytes = 0;
+uint64_t device_out_packets = 0;
+uint64_t device_out_bytes = 0;
- bool setup_device(void) {
+ static bool setup_device(void) {
- struct ifreq ifr;
- bool t1q = false;
-
if(!get_config_string(lookup_config(config_tree, "Device"), &device))
device = xstrdup(DEFAULT_DEVICE);
return false;
}
-#ifdef HAVE_LINUX_IF_TUN_H
- /* Ok now check if this is an old ethertap or a new tun/tap thingie */
-
- memset(&ifr, 0, sizeof(ifr));
+ #ifdef FD_CLOEXEC
+ fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
+
+ struct ifreq ifr = {{{0}}};
- if(routing_mode == RMODE_ROUTER) {
+ get_config_string(lookup_config(config_tree, "DeviceType"), &type);
+
+ if(type && strcasecmp(type, "tun") && strcasecmp(type, "tap")) {
+ logger(LOG_ERR, "Unknown device type %s!", type);
+ return false;
+ }
+
+ if((type && !strcasecmp(type, "tun")) || (!type && routing_mode == RMODE_ROUTER)) {
ifr.ifr_flags = IFF_TUN;
device_type = DEVICE_TYPE_TUN;
device_info = "Linux tun/tap device (tun mode)";
free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
switch(device_type) {
case DEVICE_TYPE_TUN:
return true;
}
- void dump_device_stats(void) {
+ static void dump_device_stats(void) {
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(LOG_DEBUG, " total bytes in: %10"PRIu64, device_in_bytes);
+ logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_out_bytes);
}
+
+ const devops_t os_devops = {
+ .setup = setup_device,
+ .close = close_device,
+ .read = read_packet,
+ .write = write_packet,
+ .dump_stats = dump_device_stats,
+ };
return false;
}
- bool write_packet(vpn_packet_t *packet) {
+ static bool write_packet(vpn_packet_t *packet) {
- long lenout;
+ long outlen;
OVERLAPPED overlapped = {0};
ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s",
if(c->node)
c->node->connection = NULL;
- if(c->socket)
- closesocket(c->socket);
-
if(c->edge) {
if(report && !tunnelserver)
- send_del_edge(broadcast, c->edge);
+ send_del_edge(everyone, c->edge);
edge_del(c->edge);
}
}
-/*
- this is where it all happens...
-*/
-int main_loop(void) {
- fd_set readset, writeset;
-#ifdef HAVE_PSELECT
- struct timespec tv;
- sigset_t omask, block_mask;
- time_t next_event;
-#else
- struct timeval tv;
-#endif
- int r, maxfd;
- time_t last_ping_check, last_config_check, last_graph_dump;
- event_t *event;
+static void sigterm_handler(int signal, short events, void *data) {
+ logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
+ event_loopexit(NULL);
+}
- last_ping_check = now;
- last_config_check = now;
- last_graph_dump = now;
-
- srand(now);
-
-#ifdef HAVE_PSELECT
- if(lookup_config(config_tree, "GraphDumpFile"))
- graph_dump = true;
- /* Block SIGHUP & SIGALRM */
- sigemptyset(&block_mask);
- sigaddset(&block_mask, SIGHUP);
- sigaddset(&block_mask, SIGALRM);
- sigprocmask(SIG_BLOCK, &block_mask, &omask);
-#endif
+static void sighup_handler(int signal, short events, void *data) {
+ logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
+ reopenlogger();
+ reload_configuration();
+}
- running = true;
-
- while(running) {
-#ifdef HAVE_PSELECT
- next_event = last_ping_check + pingtimeout;
- if(graph_dump && next_event > last_graph_dump + 60)
- next_event = last_graph_dump + 60;
-
- if((event = peek_next_event()) && next_event > event->time)
- next_event = event->time;
-
- if(next_event <= now)
- tv.tv_sec = 0;
- else
- tv.tv_sec = next_event - now;
- tv.tv_nsec = 0;
-#else
- tv.tv_sec = 1;
- tv.tv_usec = 0;
-#endif
+static void sigalrm_handler(int signal, short events, void *data) {
+ logger(LOG_NOTICE, "Got %s signal", strsignal(signal));
+ retry();
+}
- maxfd = build_fdset(&readset, &writeset);
+int reload_configuration(void) {
+ connection_t *c;
+ splay_node_t *node, *next;
+ char *fname;
+ struct stat s;
+ static time_t last_config_check = 0;
-#ifdef HAVE_MINGW
- LeaveCriticalSection(&mutex);
-#endif
-#ifdef HAVE_PSELECT
- r = pselect(maxfd + 1, &readset, &writeset, NULL, &tv, &omask);
-#else
- r = select(maxfd + 1, &readset, &writeset, NULL, &tv);
-#endif
- now = time(NULL);
-#ifdef HAVE_MINGW
- EnterCriticalSection(&mutex);
-#endif
+ /* Reread our own configuration file */
- if(r < 0) {
- if(!sockwouldblock(sockerrno)) {
- logger(LOG_ERR, "Error while waiting for input: %s", sockstrerror(sockerrno));
- dump_connections();
- return 1;
- }
- }
+ exit_configuration(&config_tree);
+ init_configuration(&config_tree);
- if(r > 0)
- check_network_activity(&readset, &writeset);
+ if(!read_server_config()) {
+ logger(LOG_ERR, "Unable to reread configuration file, exitting.");
+ event_loopexit(NULL);
+ return EINVAL;
+ }
- if(do_purge) {
- purge();
- do_purge = false;
+ /* Close connections to hosts that have a changed or deleted host config file */
+
+ for(node = connection_tree->head; node; node = next) {
+ c = node->data;
+ next = node->next;
+
+ if(c->outgoing) {
+ free(c->outgoing->name);
+ if(c->outgoing->ai)
+ freeaddrinfo(c->outgoing->ai);
+ free(c->outgoing);
+ c->outgoing = NULL;
}
+
+ xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
+ if(stat(fname, &s) || s.st_mtime > last_config_check)
+ terminate_connection(c, c->status.active);
+ free(fname);
+ }
- /* Let's check if everybody is still alive */
-
- if(last_ping_check + pingtimeout <= now) {
- check_dead_connections();
- last_ping_check = now;
-
- if(routing_mode == RMODE_SWITCH)
- age_subnets();
-
- age_past_requests();
-
- /* Should we regenerate our key? */
-
- if(keyexpires <= now) {
- avl_node_t *node;
- node_t *n;
-
- ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys");
+ last_config_check = time(NULL);
- for(node = node_tree->head; node; node = node->next) {
- n = node->data;
- if(n->inkey) {
- free(n->inkey);
- n->inkey = NULL;
- }
- }
+ /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
- send_key_changed();
- keyexpires = now + keylifetime;
- }
+ if(strictsubnets) {
+ subnet_t *subnet;
- /* Detect ADD_EDGE/DEL_EDGE storms that are caused when
- * two tinc daemons with the same name are on the VPN.
- * If so, sleep a while. If this happens multiple times
- * in a row, sleep longer. */
-
- if(contradicting_del_edge > 100 && contradicting_add_edge > 100) {
- logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime);
- usleep(sleeptime * 1000000LL);
- sleeptime *= 2;
- if(sleeptime < 0)
- sleeptime = 3600;
- } else {
- sleeptime /= 2;
- if(sleeptime < 10)
- sleeptime = 10;
- }
- contradicting_add_edge = 0;
- contradicting_del_edge = 0;
+ for(node = subnet_tree->head; node; node = node->next) {
+ subnet = node->data;
+ subnet->expires = 1;
}
- if(sigalrm) {
- avl_node_t *node;
- logger(LOG_INFO, "Flushing event queue");
- expire_events();
- for(node = connection_tree->head; node; node = node->next) {
- connection_t *c = node->data;
- send_ping(c);
+ load_all_subnets();
+
+ for(node = subnet_tree->head; node; node = next) {
+ next = node->next;
+ subnet = node->data;
+ if(subnet->expires == 1) {
- send_del_subnet(broadcast, subnet);
++ send_del_subnet(everyone, subnet);
+ if(subnet->owner->status.reachable)
+ subnet_update(subnet->owner, subnet, false);
+ subnet_del(subnet->owner, subnet);
+ } else if(subnet->expires == -1) {
+ subnet->expires = 0;
+ } else {
- send_add_subnet(broadcast, subnet);
++ send_add_subnet(everyone, subnet);
+ if(subnet->owner->status.reachable)
+ subnet_update(subnet->owner, subnet, true);
}
- sigalrm = false;
}
+ }
- while((event = get_expired_event())) {
- event->handler(event->data);
- free_event(event);
- }
-
- if(sighup) {
- connection_t *c;
- avl_node_t *node, *next;
- char *fname;
- struct stat s;
-
- sighup = false;
-
- reopenlogger();
-
- /* Reread our own configuration file */
-
- exit_configuration(&config_tree);
- init_configuration(&config_tree);
-
- if(!read_server_config()) {
- logger(LOG_ERR, "Unable to reread configuration file, exitting.");
- return 1;
- }
-
- /* Cancel non-active outgoing connections */
-
- for(node = connection_tree->head; node; node = next) {
- next = node->next;
- c = node->data;
-
- c->outgoing = NULL;
-
- if(c->status.connecting) {
- terminate_connection(c, false);
- connection_del(c);
- }
- }
-
- /* Wipe list of outgoing connections */
-
- for(list_node_t *node = outgoing_list->head; node; node = node->next) {
- outgoing_t *outgoing = node->data;
-
- if(outgoing->event)
- event_del(outgoing->event);
- }
-
- list_delete_list(outgoing_list);
-
- /* Close connections to hosts that have a changed or deleted host config file */
-
- for(node = connection_tree->head; node; node = node->next) {
- c = node->data;
-
- xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
- if(stat(fname, &s) || s.st_mtime > last_config_check)
- terminate_connection(c, c->status.active);
- free(fname);
- }
-
- last_config_check = now;
-
- /* If StrictSubnet is set, expire deleted Subnets and read new ones in */
-
- if(strictsubnets) {
- subnet_t *subnet;
+ /* Try to make outgoing connections */
+
+ try_outgoing_connections();
- for(node = subnet_tree->head; node; node = node->next) {
- subnet = node->data;
- subnet->expires = 1;
- }
+ return 0;
+}
- load_all_subnets();
-
- for(node = subnet_tree->head; node; node = next) {
- next = node->next;
- subnet = node->data;
- if(subnet->expires == 1) {
- send_del_subnet(everyone, subnet);
- if(subnet->owner->status.reachable)
- subnet_update(subnet->owner, subnet, false);
- subnet_del(subnet->owner, subnet);
- } else if(subnet->expires == -1) {
- subnet->expires = 0;
- } else {
- send_add_subnet(everyone, subnet);
- if(subnet->owner->status.reachable)
- subnet_update(subnet->owner, subnet, true);
- }
- }
- }
+void retry(void) {
+ connection_t *c;
+ splay_node_t *node;
- /* Try to make outgoing connections */
-
- try_outgoing_connections();
- }
+ for(node = connection_tree->head; node; node = node->next) {
+ c = node->data;
- /* Dump graph if wanted every 60 seconds*/
-
- if(last_graph_dump + 60 <= now) {
- dump_graph();
- last_graph_dump = now;
+ if(c->outgoing && !c->node) {
+ if(timeout_initialized(&c->outgoing->ev))
+ event_del(&c->outgoing->ev);
+ if(c->status.connecting)
+ close(c->socket);
+ c->outgoing->timeout = 0;
+ do_outgoing_connection(c);
}
}
+}
+
+/*
+ this is where it all happens...
+*/
+int main_loop(void) {
+ struct event timeout_event;
+
+ timeout_set(&timeout_event, timeout_handler, &timeout_event);
+ event_add(&timeout_event, &(struct timeval){pingtimeout, 0});
+
+#ifndef HAVE_MINGW
+ struct event sighup_event;
+ struct event sigterm_event;
+ struct event sigquit_event;
+ struct event sigalrm_event;
+
+ signal_set(&sighup_event, SIGHUP, sighup_handler, NULL);
+ signal_add(&sighup_event, NULL);
+ signal_set(&sigterm_event, SIGTERM, sigterm_handler, NULL);
+ signal_add(&sigterm_event, NULL);
+ signal_set(&sigquit_event, SIGQUIT, sigterm_handler, NULL);
+ signal_add(&sigquit_event, NULL);
+ signal_set(&sigalrm_event, SIGALRM, sigalrm_handler, NULL);
+ signal_add(&sigalrm_event, NULL);
+#endif
+
+ if(event_loop(0) < 0) {
+ logger(LOG_ERR, "Error while waiting for input: %s", strerror(errno));
+ return 1;
+ }
-#ifdef HAVE_PSELECT
- /* Restore SIGHUP & SIGALARM mask */
- sigprocmask(SIG_SETMASK, &omask, NULL);
+#ifndef HAVE_MINGW
+ signal_del(&sighup_event);
+ signal_del(&sigterm_event);
+ signal_del(&sigquit_event);
+ signal_del(&sigalrm_event);
#endif
+ event_del(&timeout_event);
+
return 0;
}
vpn_packet_t *inpkt = origpkt;
int nextpkt = 0;
vpn_packet_t *outpkt;
- int origlen;
- int outlen, outpad;
+ int origlen = origpkt->len;
+ size_t outlen;
#if defined(SOL_IP) && defined(IP_TOS)
static int priority = 0;
+ int origpriority = origpkt->priority;
#endif
- int sock;
- int origpriority;
if(!n->status.reachable) {
ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname);
if(n == myself) {
if(overwrite_mac)
memcpy(packet->data, mymac.x, ETH_ALEN);
- write_packet(packet);
+ n->out_packets++;
+ n->out_bytes += packet->len;
+ devops.write(packet);
return;
}
return;
}
- n->sock = sock;
++ n->sock = (intptr_t)data;
+
receive_udppacket(n, &pkt);
}
- if(read_packet(&packet)) {
+
+void handle_device_data(int sock, short events, void *data) {
+ vpn_packet_t packet;
+
+ packet.priority = 0;
+
++ if(devops.read(&packet)) {
+ myself->in_packets++;
+ myself->in_bytes += packet.len;
+ route(myself, &packet);
+ }
+}
#include "xalloc.h"
char *myport;
+static struct event device_ev;
+ devops_t devops;
-bool read_rsa_public_key(connection_t *c) {
+bool node_read_ecdsa_public_key(node_t *n) {
+ if(ecdsa_active(&n->ecdsa))
+ return true;
+
+ splay_tree_t *config_tree;
FILE *fp;
char *fname;
- char *key;
+ char *p;
+ bool result = false;
- if(!c->rsa_key) {
- c->rsa_key = RSA_new();
-// RSA_blinding_on(c->rsa_key, NULL);
- }
+ xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
- /* First, check for simple PublicKey statement */
+ init_configuration(&config_tree);
+ if(!read_config_file(config_tree, fname))
+ goto exit;
- if(get_config_string(lookup_config(c->config_tree, "PublicKey"), &key)) {
- BN_hex2bn(&c->rsa_key->n, key);
- BN_hex2bn(&c->rsa_key->e, "FFFF");
- free(key);
- return true;
+ /* First, check for simple ECDSAPublicKey statement */
+
+ if(get_config_string(lookup_config(config_tree, "ECDSAPublicKey"), &p)) {
+ result = ecdsa_set_base64_public_key(&n->ecdsa, p);
+ free(p);
+ goto exit;
}
- /* Else, check for PublicKeyFile statement and read it */
+ /* Else, check for ECDSAPublicKeyFile statement and read it */
- if(get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) {
- fp = fopen(fname, "r");
+ free(fname);
- if(!fp) {
- logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
- fname, strerror(errno));
- free(fname);
- return false;
- }
+ if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname))
+ xasprintf(&fname, "%s/hosts/%s", confbase, n->name);
- free(fname);
- c->rsa_key = PEM_read_RSAPublicKey(fp, &c->rsa_key, NULL, NULL);
- fclose(fp);
+ fp = fopen(fname, "r");
- if(c->rsa_key)
- return true; /* Woohoo. */
+ if(!fp) {
+ logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno));
+ goto exit;
+ }
- /* If it fails, try PEM_read_RSA_PUBKEY. */
- fp = fopen(fname, "r");
+ result = ecdsa_read_pem_public_key(&n->ecdsa, fp);
+ fclose(fp);
- if(!fp) {
- logger(LOG_ERR, "Error reading RSA public key file `%s': %s",
- fname, strerror(errno));
- free(fname);
- return false;
- }
+exit:
+ exit_configuration(&config_tree);
+ free(fname);
+ return result;
+}
- free(fname);
- c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
- fclose(fp);
+bool read_ecdsa_public_key(connection_t *c) {
+ FILE *fp;
+ char *fname;
+ char *p;
+ bool result;
- if(c->rsa_key) {
-// RSA_blinding_on(c->rsa_key, NULL);
- return true;
- }
+ /* First, check for simple ECDSAPublicKey statement */
- logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s",
- fname, strerror(errno));
- return false;
+ if(get_config_string(lookup_config(c->config_tree, "ECDSAPublicKey"), &p)) {
+ result = ecdsa_set_base64_public_key(&c->ecdsa, p);
+ free(p);
+ return result;
}
- /* Else, check if a harnessed public key is in the config file */
+ /* Else, check for ECDSAPublicKeyFile statement and read it */
+
+ if(!get_config_string(lookup_config(c->config_tree, "ECDSAPublicKeyFile"), &fname))
+ xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
- xasprintf(&fname, "%s/hosts/%s", confbase, c->name);
fp = fopen(fname, "r");
if(!fp) {
return false;
}
- c->rsa_key = PEM_read_RSA_PUBKEY(fp, &c->rsa_key, NULL, NULL);
-// RSA_blinding_on(c->rsa_key, NULL);
+ result = rsa_read_pem_public_key(&c->rsa, fp);
fclose(fp);
+
+ if(!result)
+ logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno));
free(fname);
+ return result;
+}
- if(c->rsa_key)
- return true;
+static bool read_ecdsa_private_key(void) {
+ FILE *fp;
+ char *fname;
+ bool result;
- logger(LOG_ERR, "No public key for %s specified!", c->name);
+ /* Check for PrivateKeyFile statement and read it */
+
+ if(!get_config_string(lookup_config(config_tree, "ECDSAPrivateKeyFile"), &fname))
+ xasprintf(&fname, "%s/ecdsa_key.priv", confbase);
+
+ fp = fopen(fname, "r");
+
+ if(!fp) {
- logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s",
- fname, strerror(errno));
++ logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s", fname, strerror(errno));
+ free(fname);
+ return false;
+ }
+
+#if !defined(HAVE_MINGW) && !defined(HAVE_CYGWIN)
+ struct stat s;
+
+ if(fstat(fileno(fp), &s)) {
+ logger(LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno));
+ free(fname);
+ return false;
+ }
- return false;
+ if(s.st_mode & ~0100700)
+ logger(LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname);
+#endif
+
+ result = ecdsa_read_pem_private_key(&myself->connection->ecdsa, fp);
+ fclose(fp);
+
+ if(!result)
+ logger(LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno));
+ free(fname);
+ return result;
}
static bool read_rsa_private_key(void) {
/* Open device */
- if(!setup_device())
+ devops = os_devops;
+
+ if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) {
+ if(!strcasecmp(type, "dummy"))
+ devops = dummy_devops;
+ else if(!strcasecmp(type, "raw_socket"))
+ devops = raw_socket_devops;
+ #ifdef ENABLE_UML
+ else if(!strcasecmp(type, "uml"))
+ devops = uml_devops;
+ #endif
+ #ifdef ENABLE_VDE
+ else if(!strcasecmp(type, "vde"))
+ devops = vde_devops;
+ #endif
+ }
+
+ if(!devops.setup())
return false;
- close_device();
+ if(device_fd >= 0) {
+ event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL);
+
+ if (event_add(&device_ev, NULL) < 0) {
+ logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++ devops.close();
+ return false;
+ }
+ }
+
/* Run tinc-up script to further initialize the tap interface */
xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
xasprintf(&envp[1], "DEVICE=%s", device ? : "");
/* Open sockets */
- get_config_string(lookup_config(config_tree, "BindToAddress"), &address);
+ listen_sockets = 0;
+ cfg = lookup_config(config_tree, "BindToAddress");
- hint.ai_family = addressfamily;
- hint.ai_socktype = SOCK_STREAM;
- hint.ai_protocol = IPPROTO_TCP;
- hint.ai_flags = AI_PASSIVE;
+ do {
+ get_config_string(cfg, &address);
+ if(cfg)
+ cfg = lookup_config_next(config_tree, cfg);
- err = getaddrinfo(address, myport, &hint, &ai);
+ hint.ai_family = addressfamily;
+ hint.ai_socktype = SOCK_STREAM;
+ hint.ai_protocol = IPPROTO_TCP;
+ hint.ai_flags = AI_PASSIVE;
- if(err || !ai) {
- logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
- gai_strerror(err));
- return false;
- }
+ err = getaddrinfo(address, myport, &hint, &ai);
+ free(address);
- listen_sockets = 0;
+ if(err || !ai) {
+ logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo",
+ gai_strerror(err));
+ return false;
+ }
- for(aip = ai; aip; aip = aip->ai_next) {
- listen_socket[listen_sockets].tcp =
- setup_listen_socket((sockaddr_t *) aip->ai_addr);
+ for(aip = ai; aip; aip = aip->ai_next) {
+ if(listen_sockets >= MAXSOCKETS) {
+ logger(LOG_ERR, "Too many listening sockets");
+ return false;
+ }
- if(listen_socket[listen_sockets].tcp < 0)
- continue;
+ listen_socket[listen_sockets].tcp =
+ setup_listen_socket((sockaddr_t *) aip->ai_addr);
- listen_socket[listen_sockets].udp =
- setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
+ if(listen_socket[listen_sockets].tcp < 0)
+ continue;
- if(listen_socket[listen_sockets].udp < 0) {
- close(listen_socket[listen_sockets].tcp);
- continue;
- }
+ listen_socket[listen_sockets].udp =
+ setup_vpn_in_socket((sockaddr_t *) aip->ai_addr);
- event_set(&listen_socket[listen_sockets].ev_tcp,
- listen_socket[listen_sockets].tcp,
- EV_READ|EV_PERSIST,
- handle_new_meta_connection, NULL);
- if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
- logger(LOG_ERR, "event_add failed: %s", strerror(errno));
- abort();
- }
- if(listen_socket[listen_sockets].udp < 0)
++ if(listen_socket[listen_sockets].udp < 0) {
++ close(listen_socket[listen_sockets].tcp);
+ continue;
++ }
+
- event_set(&listen_socket[listen_sockets].ev_udp,
- listen_socket[listen_sockets].udp,
- EV_READ|EV_PERSIST,
- handle_incoming_vpn_data, NULL);
- if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
- logger(LOG_ERR, "event_add failed: %s", strerror(errno));
- abort();
- }
++ event_set(&listen_socket[listen_sockets].ev_tcp,
++ listen_socket[listen_sockets].tcp,
++ EV_READ|EV_PERSIST,
++ handle_new_meta_connection, NULL);
++ if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) {
++ logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++ abort();
++ }
+
- ifdebug(CONNECTIONS) {
- hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
- logger(LOG_NOTICE, "Listening on %s", hostname);
- free(hostname);
- }
++ event_set(&listen_socket[listen_sockets].ev_udp,
++ listen_socket[listen_sockets].udp,
++ EV_READ|EV_PERSIST,
++ handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets);
++ if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) {
++ logger(LOG_ERR, "event_add failed: %s", strerror(errno));
++ abort();
++ }
- memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
- listen_sockets++;
+ ifdebug(CONNECTIONS) {
+ hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr);
+ logger(LOG_NOTICE, "Listening on %s", hostname);
+ free(hostname);
+ }
- if(listen_sockets >= MAXSOCKETS) {
- logger(LOG_WARNING, "Maximum of %d listening sockets reached", MAXSOCKETS);
- break;
+ memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen);
+ listen_sockets++;
}
- }
- freeaddrinfo(ai);
+ freeaddrinfo(ai);
+ } while(cfg);
if(listen_sockets)
logger(LOG_NOTICE, "Ready");
--- /dev/null
+/*
+ prf.c -- Pseudo-Random Function for key material generation
+ Copyright (C) 2011 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 <openssl/obj_mac.h>
++
+#include "digest.h"
+#include "prf.h"
+
+/* Generate key material from a master secret and a seed, based on RFC 4346 section 5.
+ We use SHA512 instead of MD5 and SHA1.
+ */
+
+static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) {
+ digest_t digest;
+
+ if(!digest_open_by_nid(&digest, nid, -1))
+ return false;
+
+ if(!digest_set_key(&digest, secret, secretlen))
+ return false;
+
+ size_t len = digest_length(&digest);
+
+ /* Data is what the "inner" HMAC function processes.
+ It consists of the previous HMAC result plus the seed.
+ */
+
+ char data[len + seedlen];
+ memset(data, 0, len);
+ memcpy(data + len, seed, seedlen);
+
+ char hash[len];
+
+ while(outlen > 0) {
+ /* Inner HMAC */
+ digest_create(&digest, data, len + seedlen, data);
+
+ /* Outer HMAC */
+ digest_create(&digest, data, len + seedlen, hash);
+
+ /* XOR the results of the outer HMAC into the out buffer */
+ for(int i = 0; i < len && i < outlen; i++)
+ *out++ ^= hash[i];
+
+ outlen -= len;
+ }
+
+ digest_close(&digest);
+ return true;
+}
+
+bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) {
+ /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */
+ memset(out, 0, outlen);
+
+ return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen);
+}
c->name, c->hostname);
}
- buffer[len++] = '\n';
+ request[len++] = '\n';
- if(c == broadcast) {
+ if(c == everyone) {
- broadcast_meta(NULL, buffer, len);
+ broadcast_meta(NULL, request, len);
return true;
} else
- return send_meta(c, buffer, len);
+ return send_meta(c, request, len);
}
-void forward_request(connection_t *from) {
- int request;
-
+void forward_request(connection_t *from, char *request) {
+ /* Note: request is not zero terminated anymore after a call to this function! */
ifdebug(PROTOCOL) {
- sscanf(from->buffer, "%d", &request);
ifdebug(META)
logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s",
- request_name[request], from->name, from->hostname,
- from->buffer);
+ request_name[atoi(request)], from->name, from->hostname, request);
else
logger(LOG_DEBUG, "Forwarding %s from %s (%s)",
- request_name[request], from->name, from->hostname);
+ request_name[atoi(request)], from->name, from->hostname);
}
- from->buffer[from->reqlen - 1] = '\n';
-
- broadcast_meta(from, from->buffer, from->reqlen);
+ int len = strlen(request);
+ request[len++] = '\n';
+ broadcast_meta(from, request, len);
}
-bool receive_request(connection_t *c) {
- int request;
+bool receive_request(connection_t *c, char *request) {
+ int reqno = atoi(request);
- if(sscanf(c->buffer, "%d", &request) == 1) {
- if((request < 0) || (request >= LAST) || !request_handlers[request]) {
+ if(reqno || *request == '0') {
+ if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) {
ifdebug(META)
logger(LOG_DEBUG, "Unknown request from %s (%s): %s",
- c->name, c->hostname, c->buffer);
+ c->name, c->hostname, request);
else
logger(LOG_ERR, "Unknown request from %s (%s)",
c->name, c->hostname);
static bool mykeyused = false;
void send_key_changed(void) {
- avl_node_t *node;
+ splay_node_t *node;
connection_t *c;
- send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name);
+ send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name);
/* Immediately send new keys to directly connected nodes to keep UDP mappings alive */
return false;
}
- memset(&ifr, 0, sizeof(ifr));
+ memset(&ifr, 0, sizeof ifr);
++
+ #ifdef FD_CLOEXEC
+ fcntl(device_fd, F_SETFD, FD_CLOEXEC);
+ #endif
+
strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ);
if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) {
close(device_fd);
free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
- if((lenin = read(device_fd, packet->data, MTU)) <= 0) {
+ if((inlen = read(device_fd, packet->data, MTU)) <= 0) {
logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
return false;
bool priorityinheritance = false;
int macexpire = 600;
bool overwrite_mac = false;
+ bool broadcast = true;
mac_t mymac = {{0xFE, 0xFD, 0, 0, 0, 0}};
+bool pcap = false;
/* Sizes of various headers */
send_packet(subnet->owner, packet);
}
+static void send_pcap(vpn_packet_t *packet) {
+ pcap = false;
+ for(splay_node_t *node = connection_tree->head; node; node = node->next) {
+ connection_t *c = node->data;
+ if(!c->status.pcap)
+ continue;
+ else
+ pcap = true;
+ if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, packet->len))
+ send_meta(c, (char *)packet->data, packet->len);
+ }
+}
+
+ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
+ uint16_t type = packet->data[12] << 8 | packet->data[13];
+
+ switch (type) {
+ case ETH_P_IP:
+ if(!checklength(source, packet, 14 + 32))
+ return false;
+
+ if(packet->data[22] < 1) {
+ route_ipv4_unreachable(source, packet, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL);
+ return false;
+ }
+
+ uint16_t old = packet->data[22] << 8 | packet->data[23];
+ packet->data[22]--;
+ uint16_t new = packet->data[22] << 8 | packet->data[23];
+
+ uint32_t checksum = packet->data[24] << 8 | packet->data[25];
+ checksum += old + (~new & 0xFFFF);
+ while(checksum >> 16)
+ checksum = (checksum & 0xFFFF) + (checksum >> 16);
+ packet->data[24] = checksum >> 8;
+ packet->data[25] = checksum & 0xff;
+
+ return true;
+
+ case ETH_P_IPV6:
+ if(!checklength(source, packet, 14 + 40))
+ return false;
+
+ if(packet->data[21] < 1) {
+ route_ipv6_unreachable(source, packet, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT);
+ return false;
+ }
+
+ packet->data[21]--;
+
+ return true;
+
+ default:
+ return true;
+ }
+ }
+
void route(node_t *source, vpn_packet_t *packet) {
+ if(pcap)
+ send_pcap(packet);
+
if(forwarding_mode == FMODE_KERNEL && source != myself) {
send_packet(myself, packet);
return;
extern rmode_t routing_mode;
extern fmode_t forwarding_mode;
+ extern bool decrement_ttl;
extern bool directonly;
extern bool overwrite_mac;
+ extern bool broadcast;
extern bool priorityinheritance;
extern int macexpire;
+extern bool pcap;
extern mac_t mymac;
free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
- if((lenin = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
+ if((inlen = read(device_fd, packet->data + 14, MTU - 14)) <= 0) {
logger(LOG_ERR, "Error while reading from %s %s: %s", device_info,
device, strerror(errno));
return false;
program_name);
else {
printf("Usage: %s [option]...\n\n", program_name);
- printf( " -c, --config=DIR Read configuration options from DIR.\n"
- " -D, --no-detach Don't fork and detach.\n"
- " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n"
- " -n, --net=NETNAME Connect to net NETNAME.\n"
- " -L, --mlock Lock tinc into main memory.\n"
- " --logfile[=FILENAME] Write log entries to a logfile.\n"
- " --pidfile=FILENAME Write PID and control socket cookie to FILENAME.\n"
- " --bypass-security Disables meta protocol security, for debugging.\n"
- " -o [HOST.]KEY=VALUE Set global/host configuration value.\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(" -c, --config=DIR Read configuration options from DIR.\n"
- " -D, --no-detach Don't fork and detach.\n"
- " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n"
- " -k, --kill[=SIGNAL] Attempt to kill a running tincd and exit.\n"
- " -n, --net=NETNAME Connect to net NETNAME.\n"
- " -K, --generate-keys[=BITS] Generate public/private RSA keypair.\n"
- " -L, --mlock Lock tinc into main memory.\n"
- " --logfile[=FILENAME] Write log entries to a logfile.\n"
- " --pidfile=FILENAME Write PID to FILENAME.\n"
- " -o, --option=[HOST.]KEY=VALUE Set global/host configuration value.\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( " -c, --config=DIR Read configuration options from DIR.\n"
++ " -D, --no-detach Don't fork and detach.\n"
++ " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n"
++ " -n, --net=NETNAME Connect to net NETNAME.\n"
++ " -L, --mlock Lock tinc into main memory.\n"
++ " --logfile[=FILENAME] Write log entries to a logfile.\n"
++ " --pidfile=FILENAME Write PID and control socket cookie to FILENAME.\n"
++ " --bypass-security Disables meta protocol security, for debugging.\n"
++ " -o, --option[HOST.]KEY=VALUE Set global/host configuration value.\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");
}
}
if(iface) free(iface);
}
- bool read_packet(vpn_packet_t *packet) {
+ static bool read_packet(vpn_packet_t *packet) {
- int lenin;
+ int inlen;
switch(state) {
case 0: {