Use the TCP socket infrastructure for control sockets.
authorGuus Sliepen <guus@sliepen.eu.org>
Sat, 7 Nov 2009 22:43:25 +0000 (23:43 +0100)
committerGuus Sliepen <guus@sliepen.eu.org>
Sat, 7 Nov 2009 22:43:25 +0000 (23:43 +0100)
The control socket code was completely different from how meta connections are
handled, resulting in lots of extra code to handle requests.  Also, not every
operating system has UNIX sockets, so we have to resort to another type of
sockets or pipes for those anyway.  To reduce code duplication and make control
sockets work the same on all platforms, we now just connect to the TCP port
where tincd is already listening on.

To authenticate, the program that wants to control a running tinc daemon must
send the contents of a cookie file. The cookie is a random 256 bits number that
is regenerated every time tincd starts. The cookie file should only be readable
by the same user that can start a tincd.

Instead of the binary-ish protocol previously used, we now use an ASCII
protocol similar to that of the meta connections, but this can still change.

18 files changed:
src/connection.c
src/connection.h
src/control.c
src/control.h
src/control_common.h
src/edge.c
src/edge.h
src/graph.c
src/graph.h
src/node.c
src/node.h
src/protocol.c
src/protocol.h
src/protocol_auth.c
src/subnet.c
src/subnet.h
src/tincctl.c
src/tincd.c

index 2372890dd393d6e9ff122a708f6ee2510c157d2a..519cf5b39bb507f0b1b997d149aa7608b6e08b6b 100644 (file)
@@ -24,6 +24,7 @@
 #include "splay_tree.h"
 #include "cipher.h"
 #include "conf.h"
+#include "control_common.h"
 #include "list.h"
 #include "logger.h"
 #include "net.h"                               /* Don't ask. */
@@ -91,20 +92,19 @@ void connection_del(connection_t *c) {
        splay_delete(connection_tree, c);
 }
 
-int dump_connections(struct evbuffer *out) {
+bool dump_connections(connection_t *cdump) {
        splay_node_t *node;
        connection_t *c;
 
        for(node = connection_tree->head; node; node = node->next) {
                c = node->data;
-               if(evbuffer_add_printf(out,
-                                  " %s at %s options %x socket %d status %04x\n",
-                                  c->name, c->hostname, c->options, c->socket,
-                                  bitfield_to_int(&c->status, sizeof c->status)) == -1)
-                       return errno;
+               send_request(cdump, "%d %d %s at %s options %x socket %d status %04x",
+                               CONTROL, REQ_DUMP_CONNECTIONS,
+                               c->name, c->hostname, c->options, c->socket,
+                               bitfield_to_int(&c->status, sizeof c->status));
        }
 
-       return 0;
+       return send_request(cdump, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
 }
 
 bool read_connection_config(connection_t *c) {
index 9476996f185149026824affde53b587fc9748b4c..6d2953124403c9092ce4602cdbbe00fea00efdc8 100644 (file)
@@ -40,7 +40,8 @@ typedef struct connection_status_t {
                int encryptout:1;                       /* 1 if we can encrypt outgoing traffic */
                int decryptin:1;                        /* 1 if we have to decrypt incoming traffic */
                int mst:1;                              /* 1 if this connection is part of a minimum spanning tree */
-               int unused:23;
+               int control:1;
+               int unused:22;
 } connection_status_t;
 
 #include "edge.h"
@@ -97,7 +98,7 @@ extern connection_t *new_connection(void) __attribute__ ((__malloc__));
 extern void free_connection(connection_t *);
 extern void connection_add(connection_t *);
 extern void connection_del(connection_t *);
-extern int dump_connections(struct evbuffer *);
+extern bool dump_connections(struct connection_t *);
 extern bool read_connection_config(connection_t *);
 
 #endif                                                 /* __TINC_CONNECTION_H__ */
index b62e1439744d3a651dbad6310434c1fbda1f008d..220781518848adbcea16cd1b789e4a1abb661788 100644 (file)
 */
 
 #include "system.h"
+#include "crypto.h"
 #include "conf.h"
 #include "control.h"
 #include "control_common.h"
 #include "graph.h"
 #include "logger.h"
+#include "protocol.h"
 #include "utils.h"
 #include "xalloc.h"
 
 static int control_socket = -1;
 static struct event control_event;
 static splay_tree_t *control_socket_tree;
-extern char *controlsocketname;
+char controlcookie[65];
+extern char *controlcookiename;
 
-static void handle_control_data(struct bufferevent *event, void *data) {
-       tinc_ctl_request_t req;
-       tinc_ctl_request_t res;
-       struct evbuffer *res_data = NULL;
-       void *req_data;
-
-       if(EVBUFFER_LENGTH(event->input) < sizeof req)
-               return;
-
-       /* Copy the structure to ensure alignment */
-       memcpy(&req, EVBUFFER_DATA(event->input), sizeof req);
-
-       if(EVBUFFER_LENGTH(event->input) < req.length)
-               return;
-       req_data = EVBUFFER_DATA(event->input) + sizeof req;
+static bool control_return(connection_t *c, int type, int error) {
+       return send_request(c, "%d %d %d", CONTROL, type, error);
+}
 
-       if(req.length < sizeof req)
-               goto failure;
+static bool control_ok(connection_t *c, int type) {
+       return control_return(c, type, 0);
+}
 
-       memset(&res, 0, sizeof res);
-       res.type = req.type;
-       res.id = req.id;
+bool control_h(connection_t *c, char *request) {
+       int type;
 
-       res_data = evbuffer_new();
-       if(res_data == NULL) {
-               res.res_errno = ENOMEM;
-               goto respond;
+       if(!c->status.control || c->allow_request != CONTROL) {
+               logger(LOG_ERR, "Unauthorized control request from %s (%s)", c->name, c->hostname);
+               return false;
        }
 
-       if(req.type == REQ_STOP) {
-               logger(LOG_NOTICE, "Got '%s' command", "stop");
-               event_loopexit(NULL);
-               goto respond;
+       if(sscanf(request, "%*d %d", &type) != 1) {
+               logger(LOG_ERR, "Got bad %s from %s (%s)", "CONTROL", c->name, c->hostname);
+               return false;
        }
 
-       if(req.type == REQ_DUMP_NODES) {
-               logger(LOG_NOTICE, "Got '%s' command", "dump nodes");
-               res.res_errno = dump_nodes(res_data);
-               goto respond;
-       }
+       switch (type) {
+               case REQ_STOP:
+                       event_loopexit(NULL);
+                       return control_ok(c, REQ_STOP);
 
-       if(req.type == REQ_DUMP_EDGES) {
-               logger(LOG_NOTICE, "Got '%s' command", "dump edges");
-               res.res_errno = dump_edges(res_data);
-               goto respond;
-       }
+               case REQ_DUMP_NODES:
+                       return dump_nodes(c);
+                       
+               case REQ_DUMP_EDGES:
+                       return dump_edges(c);
 
-       if(req.type == REQ_DUMP_SUBNETS) {
-               logger(LOG_NOTICE, "Got '%s' command", "dump subnets");
-               res.res_errno = dump_subnets(res_data);
-               goto respond;
-       }
+               case REQ_DUMP_SUBNETS:
+                       return dump_subnets(c);
 
-       if(req.type == REQ_DUMP_CONNECTIONS) {
-               logger(LOG_NOTICE, "Got '%s' command", "dump connections");
-               res.res_errno = dump_connections(res_data);
-               goto respond;
-       }
-
-       if(req.type == REQ_DUMP_GRAPH) {
-               logger(LOG_NOTICE, "Got '%s' command", "dump graph");
-               res.res_errno = dump_graph(res_data);
-               goto respond;
-       }
+               case REQ_DUMP_CONNECTIONS:
+                       return dump_connections(c);
 
-       if(req.type == REQ_PURGE) {
-               logger(LOG_NOTICE, "Got '%s' command", "purge");
-               purge();
-               goto respond;
-       }
+               case REQ_PURGE:
+                       purge();
+                       return control_ok(c, REQ_PURGE);
 
-       if(req.type == REQ_SET_DEBUG) {
-               debug_t new_debug_level;
-
-               logger(LOG_NOTICE, "Got '%s' command", "debug");
-               if(req.length != sizeof req + sizeof debug_level)
-                       res.res_errno = EINVAL;
-               else {
-                       memcpy(&new_debug_level, req_data, sizeof new_debug_level);
-                       logger(LOG_NOTICE, "Changing debug level from %d to %d",
-                                  debug_level, new_debug_level);
-                       if(evbuffer_add_printf(res_data,
-                                                                  "Changing debug level from %d to %d\n",
-                                                                  debug_level, new_debug_level) == -1)
-                               res.res_errno = errno;
-                       debug_level = new_debug_level;
+               case REQ_SET_DEBUG: {
+                       int new_level;
+                       if(sscanf(request, "%*d %*d %d", &new_level) != 1)
+                               return false;
+                       send_request(c, "%d %d %d", CONTROL, REQ_SET_DEBUG, debug_level);
+                       if(new_level >= 0)
+                               debug_level = new_level;
+                       return true;
                }
-               goto respond;
-       }
-
-       if(req.type == REQ_RETRY) {
-               logger(LOG_NOTICE, "Got '%s' command", "retry");
-               retry();
-               goto respond;
-       }
-
-       if(req.type == REQ_RELOAD) {
-               logger(LOG_NOTICE, "Got '%s' command", "reload");
-               res.res_errno = reload_configuration();
-               goto respond;
-       }
-
-       logger(LOG_DEBUG, "Malformed control command received");
-       res.res_errno = EINVAL;
-
-respond:
-       res.length = (sizeof res)
-                                + ((res_data == NULL) ? 0 : EVBUFFER_LENGTH(res_data));
-       evbuffer_drain(event->input, req.length);
-       if(bufferevent_write(event, &res, sizeof res) == -1)
-               goto failure;
-       if(res_data != NULL) {
-               if(bufferevent_write_buffer(event, res_data) == -1)
-                       goto failure;
-               evbuffer_free(res_data);
-       }
-       return;
-
-failure:
-       logger(LOG_INFO, "Closing control socket on error");
-       evbuffer_free(res_data);
-       close(event->ev_read.ev_fd);
-       splay_delete(control_socket_tree, event);
-}
 
-static void handle_control_error(struct bufferevent *event, short what, void *data) {
-       if(what & EVBUFFER_EOF)
-               logger(LOG_DEBUG, "Control socket connection closed by peer");
-       else
-               logger(LOG_DEBUG, "Error while reading from control socket: %s", strerror(errno));
+               case REQ_RETRY:
+                       retry();
+                       return control_ok(c, REQ_RETRY);
 
-       close(event->ev_read.ev_fd);
-       splay_delete(control_socket_tree, event);
-}
-
-static void handle_new_control_socket(int fd, short events, void *data) {
-       int newfd;
-       struct bufferevent *ev;
-       tinc_ctl_greeting_t greeting;
-
-       newfd = accept(fd, NULL, NULL);
-
-       if(newfd < 0) {
-               logger(LOG_ERR, "Accepting a new connection failed: %s", strerror(errno));
-               event_del(&control_event);
-               return;
-       }
-
-       ev = bufferevent_new(newfd, handle_control_data, NULL, handle_control_error, NULL);
-       if(!ev) {
-               logger(LOG_ERR, "Could not create bufferevent for new control connection: %s", strerror(errno));
-               close(newfd);
-               return;
-       }
+               case REQ_RELOAD:
+                       logger(LOG_NOTICE, "Got '%s' command", "reload");
+                       int result = reload_configuration();
+                       return control_return(c, REQ_RELOAD, result);
 
-       memset(&greeting, 0, sizeof greeting);
-       greeting.version = TINC_CTL_VERSION_CURRENT;
-       greeting.pid = getpid();
-       if(bufferevent_write(ev, &greeting, sizeof greeting) == -1) {
-               logger(LOG_ERR,
-                          "Cannot send greeting for new control connection: %s",
-                          strerror(errno));
-               bufferevent_free(ev);
-               close(newfd);
-               return;
+               default:
+                       return send_request(c, "%d %d", CONTROL, REQ_INVALID);
        }
-
-       bufferevent_enable(ev, EV_READ);
-       splay_insert(control_socket_tree, ev);
-
-       logger(LOG_DEBUG, "Control socket connection accepted");
-}
-
-static int control_compare(const struct event *a, const struct event *b) {
-       return a < b ? -1 : a > b ? 1 : 0;
 }
 
 bool init_control() {
-       int result;
+       randomize(controlcookie, sizeof controlcookie / 2);
+       bin2hex(controlcookie, controlcookie, sizeof controlcookie / 2);
+       controlcookie[sizeof controlcookie - 1] = 0;
 
-#ifdef HAVE_MINGW
-       struct sockaddr_in addr;
-       memset(&addr, 0, sizeof addr);
-       addr.sin_family = AF_INET;
-       addr.sin_addr.s_addr = htonl(0x7f000001);
-       addr.sin_port = htons(55555);
-       int option = 1;
-
-       control_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-       if(control_socket < 0) {
-               logger(LOG_ERR, "Creating control socket failed: %s", sockstrerror(sockerrno));
-               goto bail;
+       FILE *f = fopen(controlcookiename, "w");
+       if(!f) {
+               logger(LOG_ERR, "Cannot write control socket cookie file %s: %s", controlcookiename, strerror(errno));
+               return false;
        }
 
-       setsockopt(control_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option);
+#ifdef HAVE_FCHMOD
+       fchmod(f, 0600);
 #else
-       struct sockaddr_un addr;
-       char *lastslash;
-
-       if(strlen(controlsocketname) >= sizeof addr.sun_path) {
-               logger(LOG_ERR, "Control socket filename too long!");
-               goto bail;
-       }
-
-       memset(&addr, 0, sizeof addr);
-       addr.sun_family = AF_UNIX;
-       strncpy(addr.sun_path, controlsocketname, sizeof addr.sun_path - 1);
-
-       control_socket = socket(PF_UNIX, SOCK_STREAM, 0);
-
-       if(control_socket < 0) {
-               logger(LOG_ERR, "Creating UNIX socket failed: %s", strerror(errno));
-               goto bail;
-       }
-
-       /*
-        * Restrict connections to our control socket by ensuring the parent
-        * directory can be traversed only by root. Note this is not totally
-        * race-free unless all ancestors are writable only by trusted users,
-        * which we don't verify.
-        */
-
-       struct stat statbuf;
-       lastslash = strrchr(controlsocketname, '/');
-       if(lastslash != NULL) {
-               *lastslash = 0; /* temporarily change controlsocketname to be dir */
-               if(mkdir(controlsocketname, 0700) < 0 && errno != EEXIST) {
-                       logger(LOG_ERR, "Unable to create control socket directory %s: %s", controlsocketname, strerror(errno));
-                       *lastslash = '/';
-                       goto bail;
-               }
-
-               result = stat(controlsocketname, &statbuf);
-               *lastslash = '/';
-       } else
-               result = stat(".", &statbuf);
-
-       if(result < 0) {
-               logger(LOG_ERR, "Examining control socket directory failed: %s", strerror(errno));
-               goto bail;
-       }
-
-       if(statbuf.st_uid != 0 || (statbuf.st_mode & S_IXOTH) != 0 || (statbuf.st_gid != 0 && (statbuf.st_mode & S_IXGRP)) != 0) {
-               logger(LOG_ERR, "Control socket directory ownership/permissions insecure.");
-               goto bail;
-       }
+       chmod(controlcookiename, 0600);
 #endif
 
-       result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr);
-
-       if(result < 0 && sockinuse(sockerrno)) {
-#ifndef HAVE_MINGW
-               result = connect(control_socket, (struct sockaddr *)&addr, sizeof addr);
-               if(result < 0) {
-                       logger(LOG_WARNING, "Removing old control socket.");
-                       unlink(controlsocketname);
-                       result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr);
-               } else
-#endif
-               {
-                       if(netname)
-                               logger(LOG_ERR, "Another tincd is already running for net `%s'.", netname);
-                       else
-                               logger(LOG_ERR, "Another tincd is already running.");
-                       goto bail;
-               }
-       }
+       fprintf(f, "%s %s %d\n", controlcookie, myport, getpid());
+       fclose(f);
 
-       if(result < 0) {
-               logger(LOG_ERR, "Can't bind to %s: %s", controlsocketname, strerror(errno));
-               goto bail;
-       }
-
-       if(listen(control_socket, 3) < 0) {
-               logger(LOG_ERR, "Can't listen on %s: %s", controlsocketname, strerror(errno));
-               goto bail;
-       }
-
-       control_socket_tree = splay_alloc_tree((splay_compare_t)control_compare, (splay_action_t)bufferevent_free);
-
-       event_set(&control_event, control_socket, EV_READ | EV_PERSIST, handle_new_control_socket, NULL);
-       event_add(&control_event, NULL);
        return true;
-
-bail:
-       if(control_socket != -1) {
-               closesocket(control_socket);
-               control_socket = -1;
-       }
-       return false;
 }
 
 void exit_control() {
-       event_del(&control_event);
-       closesocket(control_socket);
-       unlink(controlsocketname);
+       unlink(controlcookiename);
 }
index 91708fcad7fd78a1fb05bab15ddc466f7d7ec4ad..ce8145a3510b80ca8ba04e37ad2daaa493c5ea7b 100644 (file)
@@ -22,5 +22,6 @@
 
 extern bool init_control();
 extern void exit_control();
+extern char controlcookie[];
 
 #endif
index 720c2360dfbe195b42dc89202a24a6342d8c85ab..c89f9de7dcfb61755aa64ef3a60878e1d9d48210 100644 (file)
 #ifndef __TINC_CONTROL_PROTOCOL_H__
 #define __TINC_CONTROL_PROTOCOL_H__
 
+#include "protocol.h"
+
 enum request_type {
-       REQ_STOP,
+       REQ_INVALID = -1,
+       REQ_STOP = 0,
        REQ_RELOAD,
        REQ_RESTART,
        REQ_DUMP_NODES,
@@ -36,18 +39,4 @@ enum request_type {
 
 #define TINC_CTL_VERSION_CURRENT 0
 
-/* This greeting is sent by the server on socket open. */
-typedef struct tinc_ctl_greeting_t {
-       int version;
-       pid_t pid;
-} tinc_ctl_greeting_t;
-
-/* A single request or response header. */
-typedef struct tinc_ctl_request_t {
-       size_t length; /* total length, including the header */
-       enum request_type type;
-       int id;
-       int res_errno; /* used only for responses */
-} tinc_ctl_request_t;
-
 #endif
index 80bdddbd1beb055ddd13404e65038459199476b8..f5aa0994412e2b0416467cf03ada7ce423cc3f6b 100644 (file)
@@ -21,6 +21,7 @@
 #include "system.h"
 
 #include "splay_tree.h"
+#include "control_common.h"
 #include "edge.h"
 #include "logger.h"
 #include "netutl.h"
@@ -105,7 +106,7 @@ edge_t *lookup_edge(node_t *from, node_t *to) {
        return splay_search(from->edge_tree, &v);
 }
 
-int dump_edges(struct evbuffer *out) {
+bool dump_edges(connection_t *c) {
        splay_node_t *node, *node2;
        node_t *n;
        edge_t *e;
@@ -116,16 +117,13 @@ int dump_edges(struct evbuffer *out) {
                for(node2 = n->edge_tree->head; node2; node2 = node2->next) {
                        e = node2->data;
                        address = sockaddr2hostname(&e->address);
-                       if(evbuffer_add_printf(out,
-                                                                  " %s to %s at %s options %x weight %d\n",
-                                                                  e->from->name, e->to->name, address,
-                                                                  e->options, e->weight) == -1) {
-                               free(address);
-                               return errno;
-                       }
+                       send_request(c, "%d %d %s to %s at %s options %x weight %d",
+                                       CONTROL, REQ_DUMP_EDGES,
+                                       e->from->name, e->to->name, address,
+                                       e->options, e->weight);
                        free(address);
                }
        }
 
-       return 0;
+       return send_request(c, "%d %d", CONTROL, REQ_DUMP_EDGES);
 }
index cf62b71be1f160f16f0c507c085c785b1845863f..ea45f497ec4442da08e084bda52f60c2384ff062 100644 (file)
@@ -49,6 +49,6 @@ extern void free_edge_tree(splay_tree_t *);
 extern void edge_add(edge_t *);
 extern void edge_del(edge_t *);
 extern edge_t *lookup_edge(struct node_t *, struct node_t *);
-extern int dump_edges(struct evbuffer *);
+extern bool dump_edges(struct connection_t *);
 
 #endif                                                 /* __TINC_EDGE_H__ */
index 5a0aab0ba7bd04979198492ef7ab3854b251484c..06bf36d793cc43e6d76bba2fee33df75b5f26c33 100644 (file)
@@ -382,44 +382,8 @@ void check_reachability() {
        }
 }
 
-/* Dump nodes and edges to a graphviz file.
-          
-   The file can be converted to an image with
-   dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
-*/
-
-int dump_graph(struct evbuffer *out) {
-       splay_node_t *node;
-       node_t *n;
-       edge_t *e;
-
-       if(evbuffer_add_printf(out, "digraph {\n") == -1)
-               return errno;
-       
-       /* dump all nodes first */
-       for(node = node_tree->head; node; node = node->next) {
-               n = node->data;
-               if(evbuffer_add_printf(out, "   %s [label = \"%s\"];\n",
-                                                          n->name, n->name) == -1)
-                       return errno;
-       }
-
-       /* now dump all edges */
-       for(node = edge_weight_tree->head; node; node = node->next) {
-               e = node->data;
-               if(evbuffer_add_printf(out, "   %s -> %s;\n",
-                                                          e->from->name, e->to->name) == -1)
-                       return errno;
-       }
-
-       if(evbuffer_add_printf(out, "}\n") == -1)
-               return errno;
-
-       return 0;
-}
-
 void graph(void) {
-    subnet_cache_flush();
+       subnet_cache_flush();
        sssp_dijkstra();
        check_reachability();
        mst_kruskal();
index 4f9bb5d4b46393226df28b95aa195ae2c20fc503..c8d5fda6c81ecd10c6868861ac3af34fd03923fb 100644 (file)
@@ -24,6 +24,5 @@
 extern void graph(void);
 extern void mst_kruskal(void);
 extern void sssp_bfs(void);
-extern int dump_graph(struct evbuffer *);
 
 #endif /* __TINC_GRAPH_H__ */
index 75f01e30b9a8c2f67ba94d3ad65c80b44eb0ce48..e3eca447151e228bbf307bafc8e90b93a96e5b10 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "system.h"
 
+#include "control_common.h"
 #include "splay_tree.h"
 #include "logger.h"
 #include "net.h"
@@ -154,19 +155,18 @@ void update_node_udp(node_t *n, const sockaddr_t *sa) {
        }
 }
 
-int dump_nodes(struct evbuffer *out) {
+bool dump_nodes(connection_t *c) {
        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 %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)\n",
+               send_request(c, "%d %d %s at %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)", CONTROL, REQ_DUMP_NODES,
                           n->name, n->hostname, cipher_get_nid(&n->outcipher),
                           digest_get_nid(&n->outdigest), digest_length(&n->outdigest), n->outcompression,
                           n->options, bitfield_to_int(&n->status, sizeof n->status), n->nexthop ? n->nexthop->name : "-",
-                          n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu) == -1)
-                       return errno;
+                          n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu);
        }
 
-       return 0;
+       return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES);
 }
index 02e16c6c464be1a39be258054192de2a68967290..f5ebde3b3f45be4d4591f75d8151d28f14b2c5de 100644 (file)
@@ -89,7 +89,7 @@ extern void node_add(node_t *);
 extern void node_del(node_t *);
 extern node_t *lookup_node(char *);
 extern node_t *lookup_node_udp(const sockaddr_t *);
-extern int dump_nodes(struct evbuffer *);
+extern bool dump_nodes(struct connection_t *);
 extern void update_node_udp(node_t *, const sockaddr_t *);
 
 #endif                                                 /* __TINC_NODE_H__ */
index ac4b767ea5d10d50672a39df51f7667c4db2b552..02f2841fb08859ab24cdf0a5c49e74e349a4c9ab 100644 (file)
@@ -38,7 +38,7 @@ static bool (*request_handlers[])(connection_t *, char *) = {
                ping_h, pong_h,
                add_subnet_h, del_subnet_h,
                add_edge_h, del_edge_h,
-               key_changed_h, req_key_h, ans_key_h, tcppacket_h,
+               key_changed_h, req_key_h, ans_key_h, tcppacket_h, control_h,
 };
 
 /* Request names */
@@ -48,7 +48,7 @@ static char (*request_name[]) = {
                "STATUS", "ERROR", "TERMREQ",
                "PING", "PONG",
                "ADD_SUBNET", "DEL_SUBNET",
-               "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET",
+               "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", "CONTROL",
 };
 
 static splay_tree_t *past_request_tree;
index a5ac8edc16731505018930e5cbb43d5f4f8c5b5f..f290148f90f500d71404e3b58f3eee5b72ba20ed 100644 (file)
@@ -44,6 +44,7 @@ typedef enum request_t {
        ADD_EDGE, DEL_EDGE,
        KEY_CHANGED, REQ_KEY, ANS_KEY,
        PACKET,
+       CONTROL,
        LAST                                            /* Guardian for the highest request number */
 } request_t;
 
@@ -119,5 +120,6 @@ extern bool key_changed_h(struct connection_t *, char *);
 extern bool req_key_h(struct connection_t *, char *);
 extern bool ans_key_h(struct connection_t *, char *);
 extern bool tcppacket_h(struct connection_t *, char *);
+extern bool control_h(struct connection_t *, char *);
 
 #endif                                                 /* __TINC_PROTOCOL_H__ */
index a38b9adffe6c876e0dad6a4e484353cd2426d2ec..85272301c4e3a1cfadf5e1e8862a9f75b2a0357b 100644 (file)
@@ -23,6 +23,8 @@
 #include "splay_tree.h"
 #include "conf.h"
 #include "connection.h"
+#include "control.h"
+#include "control_common.h"
 #include "crypto.h"
 #include "edge.h"
 #include "graph.h"
@@ -51,6 +53,15 @@ bool id_h(connection_t *c, char *request) {
                return false;
        }
 
+       /* Check if this is a control connection */
+
+       if(name[0] == '^' && !strcmp(name + 1, controlcookie)) {
+               c->status.control = true;
+               c->allow_request = CONTROL;
+               c->last_ping_time = time(NULL) + 3600;
+               return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid());
+       }
+
        /* Check if identity is a valid name */
 
        if(!check_id(name)) {
index 29ea96d9755276e2140a84cf9f13f53154fa1422..0669b8a0bceaf649ce39a7ae8c5338962f7c2e41 100644 (file)
@@ -21,6 +21,7 @@
 #include "system.h"
 
 #include "splay_tree.h"
+#include "control_common.h"
 #include "device.h"
 #include "logger.h"
 #include "net.h"
@@ -273,7 +274,7 @@ bool str2net(subnet_t *subnet, const char *subnetstr) {
 
 bool net2str(char *netstr, int len, const subnet_t *subnet) {
        if(!netstr || !subnet) {
-               logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!\n", netstr, subnet);
+               logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet);
                return false;
        }
 
@@ -527,7 +528,7 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) {
                free(envp[i]);
 }
 
-int dump_subnets(struct evbuffer *out) {
+bool dump_subnets(connection_t *c) {
        char netstr[MAXNETSTR];
        subnet_t *subnet;
        splay_node_t *node;
@@ -536,10 +537,10 @@ int dump_subnets(struct evbuffer *out) {
                subnet = node->data;
                if(!net2str(netstr, sizeof netstr, subnet))
                        continue;
-               if(evbuffer_add_printf(out, " %s owner %s\n",
-                                                          netstr, subnet->owner->name) == -1)
-                       return errno;
+               send_request(c, "%d %d %s owner %s",
+                               CONTROL, REQ_DUMP_SUBNETS,
+                               netstr, subnet->owner->name);
        }
 
-       return 0;
+       return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
 }
index c6aa93f2d058c023443b50b73029de4559890906..466eb20c1806f11fc9cfbea3bdbec0eedb346e09 100644 (file)
@@ -80,7 +80,7 @@ extern subnet_t *lookup_subnet(const struct node_t *, const subnet_t *);
 extern subnet_t *lookup_subnet_mac(const mac_t *);
 extern subnet_t *lookup_subnet_ipv4(const ipv4_t *);
 extern subnet_t *lookup_subnet_ipv6(const ipv6_t *);
-extern int dump_subnets(struct evbuffer *);
+extern bool dump_subnets(struct connection_t *);
 extern void subnet_cache_flush(void);
 
 #endif                                                 /* __TINC_SUBNET_H__ */
index 784c6a94759abe3e8b3a3c9e1303729f163c450b..8a07dfa020cb33951b331820a6c1c686e280f083 100644 (file)
@@ -42,8 +42,10 @@ int kill_tincd = 0;
 /* If nonzero, generate public/private keypair for this host/net. */
 int generate_keys = 0;
 
+static char *name = NULL;
 static char *identname = NULL;                         /* program name for syslog */
-static char *controlsocketname = NULL;                 /* pid file location */
+static char *controlcookiename = NULL;                 /* cookie file location */
+static char controlcookie[1024];
 char *netname = NULL;
 char *confbase = NULL;
 
@@ -69,7 +71,7 @@ static void usage(bool status) {
                printf("Valid options are:\n"
                                "  -c, --config=DIR              Read configuration options from DIR.\n"
                                "  -n, --net=NETNAME             Connect to net NETNAME.\n"
-                               "      --controlsocket=FILENAME  Open control socket at FILENAME.\n"
+                               "      --controlcookie=FILENAME  Read control socket from FILENAME.\n"
                                "      --help                    Display this help and exit.\n"
                                "      --version                 Output version information and exit.\n"
                                "\n"
@@ -121,7 +123,7 @@ static bool parse_options(int argc, char **argv) {
                                break;
 
                        case 5:                                 /* open control socket here */
-                               controlsocketname = xstrdup(optarg);
+                               controlcookiename = xstrdup(optarg);
                                break;
 
                        case '?':
@@ -196,7 +198,6 @@ FILE *ask_and_open(const char *filename, const char *what, const char *mode) {
 static bool keygen(int bits) {
        rsa_t key;
        FILE *f;
-       char *name = NULL;
        char *filename;
 
        fprintf(stderr, "Generating %d bits keys:\n", bits);
@@ -272,14 +273,16 @@ static void make_names(void) {
                                        xasprintf(&confbase, "%s", installdir);
                        }
                }
+               if(!controlcookiename)
+                       xasprintf(&controlcookiename, "%s/cookie", confbase);
                RegCloseKey(key);
                if(*installdir)
                        return;
        }
 #endif
 
-       if(!controlsocketname)
-               xasprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
+       if(!controlcookiename)
+               xasprintf(&controlcookiename, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
 
        if(netname) {
                if(!confbase)
@@ -292,123 +295,67 @@ static void make_names(void) {
        }
 }
 
-static int fullread(int fd, void *data, size_t datalen) {
-       int rv, len = 0;
+static bool recvline(int fd, char *line, size_t len) {
+       static char buffer[4096];
+       static size_t blen = 0;
+       char *newline = NULL;
 
-       while(len < datalen) {
-               rv = recv(fd, data + len, datalen - len, 0);
-               if(rv == -1 && errno == EINTR)
+       while(!(newline = memchr(buffer, '\n', blen))) {
+               int result = recv(fd, buffer + blen, sizeof buffer - blen, 0);
+               if(result == -1 && errno == EINTR)
                        continue;
-               else if(rv == -1)
-                       return rv;
-               else if(rv == 0) {
-#ifdef HAVE_MINGW
-                       errno = 0;
-#else
-                       errno = ENODATA;
-#endif
-                       return -1;
-               }
-               len += rv;
+               else if(result <= 0)
+                       return false;
+               blen += result;
        }
-       return 0;
-}
-
-/*
-   Send a request (raw)
-*/
-static int send_ctl_request(int fd, enum request_type type,
-                                                  void const *outdata, size_t outdatalen,
-                                                  int *res_errno_p, void **indata_p,
-                                                  size_t *indatalen_p) {
-       tinc_ctl_request_t req;
-       int rv;
-       void *indata;
-
-       memset(&req, 0, sizeof req);
-       req.length = sizeof req + outdatalen;
-       req.type = type;
-       req.res_errno = 0;
-
-#ifdef HAVE_MINGW
-       if(send(fd, (void *)&req, sizeof req, 0) != sizeof req || send(fd, outdata, outdatalen, 0) != outdatalen)
-               return -1;
-#else
-       struct iovec vector[2] = {
-               {&req, sizeof req},
-               {(void*) outdata, outdatalen}
-       };
-
-       if(res_errno_p == NULL)
-               return -1;
 
-       while((rv = writev(fd, vector, 2)) == -1 && errno == EINTR) ;
-       if(rv != req.length)
-               return -1;
-#endif
-
-       if(fullread(fd, &req, sizeof req) == -1)
-               return -1;
-
-       if(req.length < sizeof req) {
-               errno = EINVAL;
-               return -1;
-       }
+       if(newline - buffer >= len)
+               return false;
 
-       if(req.length > sizeof req) {
-               if(indata_p == NULL) {
-                       errno = EINVAL;
-                       return -1;
-               }
+       len = newline - buffer;
 
-               indata = xmalloc(req.length - sizeof req);
+       memcpy(line, buffer, len);
+       line[len] = 0;
+       memmove(buffer, newline + 1, blen - len - 1);
+       blen -= len + 1;
 
-               if(fullread(fd, indata, req.length - sizeof req) == -1) {
-                       free(indata);
-                       return -1;
-               }
+       return true;
+}
 
-               *indata_p = indata;
-               if(indatalen_p != NULL)
-                       *indatalen_p = req.length - sizeof req;
-       }
+static bool sendline(int fd, char *format, ...) {
+       static char buffer[4096];
+       char *p = buffer;
+       size_t blen = 0;
+       va_list ap;
 
-       *res_errno_p = req.res_errno;
+       va_start(ap, format);
+       blen = vsnprintf(buffer, sizeof buffer, format, ap);
+       va_end(ap);
 
-       return 0;
-}
-
-/*
-   Send a request (with printfs)
-*/
-static int send_ctl_request_cooked(int fd, enum request_type type, void const *outdata, size_t outdatalen) {
-       int res_errno = -1;
-       char *buf = NULL;
-       size_t buflen = 0;
-
-       if(send_ctl_request(fd, type, outdata, outdatalen, &res_errno,
-                                               (void**) &buf, &buflen)) {
-               fprintf(stderr, "Error sending request: %s\n", strerror(errno));
-               return -1;
-       }
+       if(blen < 0 || blen >= sizeof buffer)
+               return false;
 
-       if(buf != NULL) {
-               printf("%*s", (int)buflen, buf);
-               free(buf);
-       }
+       buffer[blen] = '\n';
+       blen++;
 
-       if(res_errno != 0) {
-               fprintf(stderr, "Server reported error: %s\n", strerror(res_errno));
-               return -1;
+       while(blen) {
+               int result = send(fd, p, blen, 0);
+               if(result == -1 && errno == EINTR)
+                       continue;
+               else if(result <= 0);
+                       return false;
+               p += result;
+               blen -= result;
        }
 
-       return 0;
+       return true;    
 }
 
 int main(int argc, char *argv[], char *envp[]) {
-       tinc_ctl_greeting_t greeting;
        int fd;
        int result;
+       int port;
+       int pid;
 
        program_name = argv[0];
 
@@ -460,17 +407,28 @@ int main(int argc, char *argv[], char *envp[]) {
         * ancestors are writable only by trusted users, which we don't verify.
         */
 
+       FILE *f = fopen(controlcookiename, "r");
+       if(!f) {
+               fprintf(stderr, "Could not open control socket cookie file %s: %s\n", controlcookiename, strerror(errno));
+               return 1;
+       }
+       if(fscanf(f, "%1024s %d %d", controlcookie, &port, &pid) != 3) {
+               fprintf(stderr, "Could not parse control socket cookie file %s\n", controlcookiename);
+               return 1;
+       }
+
 #ifdef HAVE_MINGW
        if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) {
                fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError()));
                return 1;
        }
+#endif
 
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof addr);
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(0x7f000001);
-       addr.sin_port = htons(55555);
+       addr.sin_port = htons(port);
 
        fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(fd < 0) {
@@ -483,77 +441,69 @@ int main(int argc, char *argv[], char *envp[]) {
        if(ioctlsocket(fd, FIONBIO, &arg) != 0) {
                fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno));
        }
-#else
-       struct sockaddr_un addr;
-       struct stat statbuf;
-       char *lastslash = strrchr(controlsocketname, '/');
-       if(lastslash != NULL) {
-               /* control socket is not in cwd; stat its parent */
-               *lastslash = 0;
-               result = stat(controlsocketname, &statbuf);
-               *lastslash = '/';
-       } else
-               result = stat(".", &statbuf);
-
-       if(result < 0) {
-               fprintf(stderr, "Unable to check control socket directory permissions: %s\n", strerror(errno));
-               return 1;
-       }
-
-       if(statbuf.st_uid != 0 || (statbuf.st_mode & S_IXOTH) != 0 || (statbuf.st_gid != 0 && (statbuf.st_mode & S_IXGRP)) != 0) {
-               fprintf(stderr, "Insecure permissions on control socket directory\n");
-               return 1;
-       }
-
-       if(strlen(controlsocketname) >= sizeof addr.sun_path) {
-               fprintf(stderr, "Control socket filename too long!\n");
-               return 1;
-       }
-
-       fd = socket(PF_UNIX, SOCK_STREAM, 0);
-       if(fd < 0) {
-               fprintf(stderr, "Cannot create UNIX socket: %s\n", strerror(errno));
-               return 1;
-       }
-
-       memset(&addr, 0, sizeof addr);
-       addr.sun_family = AF_UNIX;
-       strncpy(addr.sun_path, controlsocketname, sizeof addr.sun_path - 1);
-#endif
 
        if(connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
                        
-               fprintf(stderr, "Cannot connect to %s: %s\n", controlsocketname, sockstrerror(sockerrno));
+               fprintf(stderr, "Cannot connect to %s: %s\n", controlcookiename, sockstrerror(sockerrno));
                return 1;
        }
 
-       if(fullread(fd, &greeting, sizeof greeting) == -1) {
+       char line[4096];
+       char data[4096];
+       int code, version, req;
+
+       if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) {
                fprintf(stderr, "Cannot read greeting from control socket: %s\n",
                                sockstrerror(sockerrno));
                return 1;
        }
 
-       if(greeting.version != TINC_CTL_VERSION_CURRENT) {
-               fprintf(stderr, "Version mismatch: server %d, client %d\n",
-                               greeting.version, TINC_CTL_VERSION_CURRENT);
+       sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT);
+       
+       if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) {
+               fprintf(stderr, "Could not fully establish control socket connection\n");
                return 1;
        }
 
        if(!strcasecmp(argv[optind], "pid")) {
-               printf("%d\n", greeting.pid);
+               printf("%d\n", pid);
                return 0;
        }
 
        if(!strcasecmp(argv[optind], "stop")) {
-               return send_ctl_request_cooked(fd, REQ_STOP, NULL, 0) != -1;
+               sendline(fd, "%d %d", CONTROL, REQ_STOP);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) {
+                       fprintf(stderr, "Could not stop tinc daemon\n");
+                       return 1;
+               }
+               return 0;
        }
 
        if(!strcasecmp(argv[optind], "reload")) {
-               return send_ctl_request_cooked(fd, REQ_RELOAD, NULL, 0) != -1;
+               sendline(fd, "%d %d", CONTROL, REQ_RELOAD);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) {
+                       fprintf(stderr, "Could not reload tinc daemon\n");
+                       return 1;
+               }
+               return 0;
        }
-       
+
        if(!strcasecmp(argv[optind], "restart")) {
-               return send_ctl_request_cooked(fd, REQ_RESTART, NULL, 0) != -1;
+               sendline(fd, "%d %d", CONTROL, REQ_RESTART);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RESTART || result) {
+                       fprintf(stderr, "Could not restart tinc daemon\n");
+                       return 1;
+               }
+               return 0;
+       }
+
+       if(!strcasecmp(argv[optind], "retry")) {
+               sendline(fd, "%d %d", CONTROL, REQ_RETRY);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) {
+                       fprintf(stderr, "Could not retry outgoing connections\n");
+                       return 1;
+               }
+               return 0;
        }
 
        if(!strcasecmp(argv[optind], "dump")) {
@@ -563,53 +513,84 @@ int main(int argc, char *argv[], char *envp[]) {
                        return 1;
                }
 
-               if(!strcasecmp(argv[optind+1], "nodes")) {
-                       return send_ctl_request_cooked(fd, REQ_DUMP_NODES, NULL, 0) != -1;
-               }
-
-               if(!strcasecmp(argv[optind+1], "edges")) {
-                       return send_ctl_request_cooked(fd, REQ_DUMP_EDGES, NULL, 0) != -1;
-               }
-
-               if(!strcasecmp(argv[optind+1], "subnets")) {
-                       return send_ctl_request_cooked(fd, REQ_DUMP_SUBNETS, NULL, 0) != -1;
+               bool do_graph = false;
+               int dumps = 1;
+
+               if(!strcasecmp(argv[optind+1], "nodes"))
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
+               else if(!strcasecmp(argv[optind+1], "edges"))
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
+               else if(!strcasecmp(argv[optind+1], "subnets"))
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS);
+               else if(!strcasecmp(argv[optind+1], "connections"))
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS);
+               else if(!strcasecmp(argv[optind+1], "graph")) {
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES);
+                       sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES);
+                       do_graph = true;
+                       dumps = 2;
+                       printf("digraph {\n");
+               } else {
+                       fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]);
+                       usage(true);
+                       return 1;
                }
 
-               if(!strcasecmp(argv[optind+1], "connections")) {
-                       return send_ctl_request_cooked(fd, REQ_DUMP_CONNECTIONS, NULL, 0) != -1;
-               }
+               while(recvline(fd, line, sizeof line)) {
+                       char node1[4096], node2[4096];
+                       int n = sscanf(line, "%d %d %s to %s", &code, &req, &node1, &node2);
+                       if(n == 2) {
+                               if(do_graph && req == REQ_DUMP_NODES)
+                                       continue;
+                               else {
+                                       if(do_graph)
+                                               printf("}\n");
+                                       return 0;
+                               }
+                       }
+                       if(n < 2)
+                               break;
 
-               if(!strcasecmp(argv[optind+1], "graph")) {
-                       return send_ctl_request_cooked(fd, REQ_DUMP_GRAPH, NULL, 0) != -1;
+                       if(!do_graph)
+                               printf("%s\n", line + 5);
+                       else {
+                               if(req == REQ_DUMP_NODES)
+                                       printf(" %s [label = \"%s\"];\n", node1, node1);
+                               else
+                                       printf(" %s -> %s;\n", node1, node2);
+                       }
                }
 
-               fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]);
-               usage(true);
+               fprintf(stderr, "Error receiving dump\n");
                return 1;
        }
 
        if(!strcasecmp(argv[optind], "purge")) {
-               return send_ctl_request_cooked(fd, REQ_PURGE, NULL, 0) != -1;
+               sendline(fd, "%d %d", CONTROL, REQ_PURGE);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) {
+                       fprintf(stderr, "Could not purge tinc daemon\n");
+                       return 1;
+               }
+               return 0;
        }
 
        if(!strcasecmp(argv[optind], "debug")) {
-               int debuglevel;
+               int debuglevel, origlevel;
 
                if(argc != optind + 2) {
                        fprintf(stderr, "Invalid arguments.\n");
                        return 1;
                }
                debuglevel = atoi(argv[optind+1]);
-               return send_ctl_request_cooked(fd, REQ_SET_DEBUG, &debuglevel,
-                                                                          sizeof debuglevel) != -1;
-       }
 
-       if(!strcasecmp(argv[optind], "retry")) {
-               return send_ctl_request_cooked(fd, REQ_RETRY, NULL, 0) != -1;
-       }
+               sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel);
+               if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_SET_DEBUG) {
+                       fprintf(stderr, "Could not purge tinc daemon\n");
+                       return 1;
+               }
 
-       if(!strcasecmp(argv[optind], "reload")) {
-               return send_ctl_request_cooked(fd, REQ_RELOAD, NULL, 0) != -1;
+               fprintf(stderr, "Old level %d, new level %d\n", origlevel, debuglevel);
+               return 0;
        }
 
        fprintf(stderr, "Unknown command `%s'.\n", argv[optind]);
index 1761dc212ad46c173d881a42c1d9f759e8d289f9..f6cda972e7fa62c0a682c85e2703d12b9821e277 100644 (file)
@@ -78,8 +78,8 @@ static const char *switchuser = NULL;
 bool use_logfile = false;
 
 char *identname = NULL;                                /* program name for syslog */
-char *controlsocketname = NULL;                        /* control socket location */
 char *logfilename = NULL;                      /* log file location */
+char *controlcookiename = NULL;
 char **g_argv;                                 /* a copy of the cmdline arguments */
 
 static int status;
@@ -96,7 +96,7 @@ static struct option const long_options[] = {
        {"chroot", no_argument, NULL, 'R'},
        {"user", required_argument, NULL, 'U'},
        {"logfile", optional_argument, NULL, 4},
-       {"controlsocket", required_argument, NULL, 5},
+       {"controlcookie", required_argument, NULL, 5},
        {NULL, 0, NULL, 0}
 };
 
@@ -117,7 +117,7 @@ static void usage(bool status) {
                                "  -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"
-                               "      --controlsocket=FILENAME  Open control socket at FILENAME.\n"
+                               "      --controlcookie=FILENAME  Write control socket cookie to FILENAME.\n"
                                "      --bypass-security         Disables meta protocol security, for debugging.\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"
@@ -190,7 +190,7 @@ static bool parse_options(int argc, char **argv) {
                                break;
 
                        case 5:                                 /* open control socket here */
-                               controlsocketname = xstrdup(optarg);
+                               controlcookiename = xstrdup(optarg);
                                break;
 
                        case '?':
@@ -231,6 +231,8 @@ static void make_names(void) {
                                else
                                        xasprintf(&confbase, "%s", installdir);
                        }
+                       if(!controlcookiename)
+                               xasprintf(&controlcookiename, "%s/cookie", confbase);
                }
                RegCloseKey(key);
                if(*installdir)
@@ -238,9 +240,6 @@ static void make_names(void) {
        }
 #endif
 
-       if(!controlsocketname)
-               xasprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
-
        if(!logfilename)
                xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname);
 
@@ -258,7 +257,7 @@ static void make_names(void) {
 static void free_names() {
        if (identname) free(identname);
        if (netname) free(netname);
-       if (controlsocketname) free(controlsocketname);
+       if (controlcookiename) free(controlcookiename);
        if (logfilename) free(logfilename);
        if (confbase) free(confbase);
 }
@@ -359,9 +358,6 @@ int main(int argc, char **argv) {
                return 1;
        }
 
-       if(!init_control())
-               return 1;
-
        g_argv = argv;
 
        init_configuration(&config_tree);
@@ -410,6 +406,9 @@ int main2(int argc, char **argv) {
        if(!setup_network())
                goto end;
 
+       if(!init_control())
+               return 1;
+
        /* Initiate all outgoing connections. */
 
        try_outgoing_connections();
@@ -449,9 +448,7 @@ int main2(int argc, char **argv) {
 end:
        logger(LOG_NOTICE, "Terminating");
 
-#ifndef HAVE_MINGW
        exit_control();
-#endif
 
        crypto_exit();