changeset 11572:659bb1a26da4 HEAD

director: Added initial testing framework and some debugging output.
author Timo Sirainen <tss@iki.fi>
date Thu, 17 Jun 2010 19:18:34 +0100
parents 75d5e31ea8cc
children 3128f592ef5c
files .hgignore src/director/Makefile.am src/director/director-connection.c src/director/director-test.c src/director/director.c src/director/director.h src/director/main.c
diffstat 7 files changed, 524 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Jun 17 19:16:09 2010 +0100
+++ b/.hgignore	Thu Jun 17 19:18:34 2010 +0100
@@ -62,6 +62,7 @@
 src/lda/dovecot-lda
 src/dict/dict
 src/director/director
+src/director/director-test
 src/dns/dns-client
 src/doveadm/doveadm
 src/dsync/dsync
--- a/src/director/Makefile.am	Thu Jun 17 19:16:09 2010 +0100
+++ b/src/director/Makefile.am	Thu Jun 17 19:18:34 2010 +0100
@@ -5,6 +5,7 @@
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib \
 	-I$(top_srcdir)/src/lib-auth \
+	-I$(top_srcdir)/src/lib-imap \
 	-I$(top_srcdir)/src/lib-settings \
 	-I$(top_srcdir)/src/lib-master \
 	-DPKG_RUNDIR=\""$(rundir)"\"
@@ -38,3 +39,11 @@
 	mail-host.h \
 	notify-connection.h \
 	user-directory.h
+
+noinst_PROGRAMS = director-test
+
+director_test_LDADD = $(LIBDOVECOT)
+director_test_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+
+director_test_SOURCES = \
+	director-test.c
--- a/src/director/director-connection.c	Thu Jun 17 19:16:09 2010 +0100
+++ b/src/director/director-connection.c	Thu Jun 17 19:18:34 2010 +0100
@@ -527,10 +527,19 @@
 	}
 
 	/* remote suggests us to connect elsewhere */
+	if (dir->debug) {
+		i_debug("Received CONNECT request to %s, "
+			"current right is %s", host->name,
+			dir->right == NULL ? "<none>" :
+			dir->right->name);
+	}
+
 	if (dir->right != NULL &&
 	    director_host_cmp_to_self(host, dir->right->host,
 				      dir->self_host) <= 0) {
 		/* the old connection is the correct one */
+		if (dir->debug)
+			i_debug("Ignoring CONNECT");
 		return TRUE;
 	}
 
@@ -783,6 +792,11 @@
 
 	*_conn = NULL;
 
+	if (conn->dir->debug && conn->host != NULL) {
+		i_debug("Director %s:%u disconnected",
+			net_ip2addr(&conn->host->ip), conn->host->port);
+	}
+
 	if (conn->dir->left == conn)
 		conn->dir->left = NULL;
 	if (conn->dir->right == conn)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/director/director-test.c	Thu Jun 17 19:18:34 2010 +0100
@@ -0,0 +1,457 @@
+/* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */
+
+/*
+   This program accepts incoming unauthenticated IMAP connections from
+   port 14300. If the same user is connecting to multiple different local IPs,
+   it logs an error (i.e. director is not working right then).
+
+   This program also accepts incoming director connections on port 9090 and
+   forwards them to local_ip:9091. So all directors think the others are
+   listening on port 9091, while in reality all of them are on 9090.
+   The idea is that this test tool hooks between all director connections and
+   can then add delays or break the connections.
+
+   Finally, this program connects to director-admin socket where it adds
+   and removes mail hosts.
+*/
+
+#include "lib.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "hash.h"
+#include "llist.h"
+#include "imap-parser.h"
+#include "master-service.h"
+#include "master-service-settings.h"
+#include "director-settings.h"
+
+#define IMAP_PORT 14300
+#define DIRECTOR_IN_PORT 9091
+#define DIRECTOR_OUT_PORT 9090
+#define USER_TIMEOUT_MSECS (1000*60)
+
+struct user {
+	char *username;
+	struct ip_addr local_ip;
+
+	time_t last_seen;
+	unsigned int connections;
+
+	struct timeout *to;
+};
+
+struct imap_client {
+	struct imap_client *prev, *next;
+
+	int fd;
+	struct io *io;
+	struct istream *input;
+	struct ostream *output;
+	struct imap_parser *parser;
+	struct user *user;
+
+	char *username;
+};
+
+struct director_connection {
+	struct director_connection *prev, *next;
+
+	int in_fd, out_fd;
+	struct io *in_io, *out_io;
+	struct istream *in_input, *out_input;
+	struct ostream *in_output, *out_output;
+};
+
+struct admin_connection {
+	char *path;
+	int fd;
+	struct istream *input;
+};
+
+static struct imap_client *imap_clients;
+static struct director_connection *director_connections;
+static struct hash_table *users;
+static struct admin_connection *admin;
+
+static void imap_client_destroy(struct imap_client **client);
+static void director_connection_destroy(struct director_connection **_conn);
+
+static void client_username_check(struct imap_client *client)
+{
+	struct user *user;
+	struct ip_addr local_ip;
+
+	if (net_getsockname(client->fd, &local_ip, NULL) < 0)
+		i_fatal("net_getsockname() failed: %m");
+
+	user = hash_table_lookup(users, client->username);
+	if (user == NULL) {
+		user = i_new(struct user, 1);
+		user->username = i_strdup(client->username);
+		user->local_ip = local_ip;
+		hash_table_insert(users, user->username, user);
+	} else if (!net_ip_compare(&user->local_ip, &local_ip)) {
+		i_error("user %s: old connection from %s, new from %s. "
+			"%u old connections, last was %u secs ago",
+			user->username, net_ip2addr(&user->local_ip),
+			net_ip2addr(&local_ip), user->connections,
+			(unsigned int)(ioloop_time - user->last_seen));
+		return;
+	}
+	client->user = user;
+	user->connections++;
+	user->last_seen = ioloop_time;
+
+	if (user->to != NULL)
+		timeout_remove(&user->to);
+}
+
+static void user_free(struct user *user)
+{
+	if (user->to != NULL)
+		timeout_remove(&user->to);
+	hash_table_remove(users, user->username);
+	i_free(user->username);
+	i_free(user);
+}
+
+static int imap_client_parse_input(struct imap_client *client)
+{
+	const char *tag, *cmd, *str;
+	const struct imap_arg *args;
+	int ret;
+
+	ret = imap_parser_read_args(client->parser, 0, 0, &args);
+	if (ret < 0) {
+		if (ret == -2)
+			return 0;
+		return -1;
+	}
+
+	if (!imap_arg_get_atom(args, &tag))
+		return -1;
+	args++;
+
+	if (!imap_arg_get_atom(args, &cmd))
+		return -1;
+	args++;
+
+	if (strcasecmp(cmd, "login") == 0) {
+		if (client->username != NULL)
+			return -1;
+
+		if (!imap_arg_get_astring(args, &str))
+			return -1;
+
+		o_stream_send_str(client->output,
+			t_strconcat(tag, " OK Logged in.\r\n", NULL));
+		client->username = i_strdup(str);
+		client_username_check(client);
+	} else if (strcasecmp(cmd, "logout") == 0) {
+		o_stream_send_str(client->output, t_strconcat(
+			"* BYE Out.\r\n",
+			tag, " OK Logged out.\r\n", NULL));
+		imap_client_destroy(&client);
+		return 0;
+	} else if (strcasecmp(cmd, "capability") == 0) {
+		o_stream_send_str(client->output,
+			t_strconcat("* CAPABILITY IMAP4rev1\r\n",
+				    tag, " OK Done.\r\n", NULL));
+	} else {
+		o_stream_send_str(client->output,
+			t_strconcat(tag, " BAD Not supported.\r\n", NULL));
+	}
+
+	(void)i_stream_read_next_line(client->input); /* eat away LF */
+	imap_parser_reset(client->parser);
+	return 1;
+}
+
+static void imap_client_input(struct imap_client *client)
+{
+	int ret;
+
+	switch (i_stream_read(client->input)) {
+	case -2:
+		i_error("imap: Too much input");
+		imap_client_destroy(&client);
+		return;
+	case -1:
+		imap_client_destroy(&client);
+		return;
+	default:
+		break;
+	}
+
+	while ((ret = imap_client_parse_input(client)) > 0) ;
+	if (ret < 0) {
+		i_error("imap: Invalid input");
+		imap_client_destroy(&client);
+	}
+}
+
+static void imap_client_create(int fd)
+{
+	struct imap_client *client;
+
+	client = i_new(struct imap_client, 1);
+	client->fd = fd;
+	client->input = i_stream_create_fd(fd, 4096, FALSE);
+	client->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+	client->io = io_add(fd, IO_READ, imap_client_input, client);
+	client->parser =
+		imap_parser_create(client->input, client->output, 4096);
+	o_stream_send_str(client->output,
+		"* OK [CAPABILITY IMAP4rev1] director-test ready.\r\n");
+	DLLIST_PREPEND(&imap_clients, client);
+}
+
+static void imap_client_destroy(struct imap_client **_client)
+{
+	struct imap_client *client = *_client;
+	struct user *user = client->user;
+
+	*_client = NULL;
+
+	if (user != NULL) {
+		i_assert(user->connections > 0);
+		if (--user->connections == 0) {
+			i_assert(user->to == NULL);
+			user->to = timeout_add(USER_TIMEOUT_MSECS, user_free,
+					       user);
+		}
+		user->last_seen = ioloop_time;
+	}
+
+	DLLIST_REMOVE(&imap_clients, client);
+	imap_parser_destroy(&client->parser);
+	io_remove(&client->io);
+	i_stream_unref(&client->input);
+	o_stream_unref(&client->output);
+	net_disconnect(client->fd);
+	i_free(client->username);
+	i_free(client);
+
+	master_service_client_connection_destroyed(master_service);
+}
+
+static const char *director_line_update_port(const char *line)
+{
+	const char *p, *prefix, *suffix;
+	unsigned int i = 0;
+
+	/* <cmd> \t IP \t <port> [\t more] */
+	for (p = line;; p++) {
+		if (*p == '\0') {
+			i_error("director: Invalid input: %s", line);
+			return line;
+		}
+		if (*p == '\t') {
+			if (++i == 2)
+				break;
+		}
+	}
+	prefix = t_strdup_until(line, ++p);
+	suffix = strchr(p, '\t');
+	return t_strdup_printf("%s%u%s", prefix, DIRECTOR_OUT_PORT,
+			       suffix != NULL ? suffix : "");
+}
+
+static void
+director_connection_input(struct director_connection *conn,
+			  struct istream *input, struct ostream *output)
+{
+	const char *line;
+
+	o_stream_cork(output);
+	while ((line = i_stream_read_next_line(input)) != NULL) {
+#if 0
+		if (strncmp(line, "ME\t", 3) == 0 ||
+		    strncmp(line, "DIRECTOR\t", 9) == 0 ||
+		    strncmp(line, "SYNC\t", 5) == 0) {
+			const char *orig = line;
+
+			line = director_line_update_port(line);
+		}
+#endif
+		o_stream_send_str(output, line);
+		o_stream_send(output, "\n", 1);
+	}
+	o_stream_uncork(output);
+	if (input->stream_errno != 0 || input->eof) {
+		director_connection_destroy(&conn);
+		return;
+	}
+}
+
+static void director_connection_in_input(struct director_connection *conn)
+{
+	director_connection_input(conn, conn->in_input, conn->out_output);
+}
+
+static void director_connection_out_input(struct director_connection *conn)
+{
+	director_connection_input(conn, conn->out_input, conn->in_output);
+}
+
+static void
+director_connection_create(int in_fd, const struct ip_addr *local_ip)
+{
+	struct director_connection *conn;
+
+	conn = i_new(struct director_connection, 1);
+	conn->in_fd = in_fd;
+	conn->in_input = i_stream_create_fd(conn->in_fd, (size_t)-1, FALSE);
+	conn->in_output = o_stream_create_fd(conn->in_fd, (size_t)-1, FALSE);
+	conn->in_io = io_add(conn->in_fd, IO_READ,
+			     director_connection_in_input, conn);
+
+	conn->out_fd = net_connect_ip(local_ip, DIRECTOR_OUT_PORT, NULL);
+	conn->out_input = i_stream_create_fd(conn->out_fd, (size_t)-1, FALSE);
+	conn->out_output = o_stream_create_fd(conn->out_fd, (size_t)-1, FALSE);
+	conn->out_io = io_add(conn->out_fd, IO_READ,
+			      director_connection_out_input, conn);
+
+	DLLIST_PREPEND(&director_connections, conn);
+}
+
+static void director_connection_destroy(struct director_connection **_conn)
+{
+	struct director_connection *conn = *_conn;
+
+	DLLIST_REMOVE(&director_connections, conn);
+
+	io_remove(&conn->in_io);
+	i_stream_unref(&conn->in_input);
+	o_stream_unref(&conn->in_output);
+	net_disconnect(conn->in_fd);
+
+	io_remove(&conn->out_io);
+	i_stream_unref(&conn->out_input);
+	o_stream_unref(&conn->out_output);
+	net_disconnect(conn->out_fd);
+
+	i_free(conn);
+}
+
+static void client_connected(struct master_service_connection *conn)
+{
+	struct ip_addr local_ip;
+	unsigned int local_port;
+
+	if (net_getsockname(conn->fd, &local_ip, &local_port) < 0)
+		i_fatal("net_getsockname() failed: %m");
+
+	if (local_port == IMAP_PORT)
+		imap_client_create(conn->fd);
+	else if (local_port == DIRECTOR_IN_PORT)
+		director_connection_create(conn->fd, &local_ip);
+	else {
+		i_error("Connection to unknown port %u", local_port);
+		return;
+	}
+	master_service_client_connection_accept(conn);
+}
+
+static void
+admin_send(struct admin_connection *conn, const char *data)
+{
+	if (write_full(i_stream_get_fd(conn->input), data, strlen(data)) < 0)
+		i_fatal("write(%s) failed: %m", conn->path);
+}
+
+static struct admin_connection *admin_connect(const char *path)
+{
+#define DIRECTOR_ADMIN_HANDSHAKE "VERSION\tdirector-doveadm\t1\t0\n"
+	struct admin_connection *conn;
+	const char *line;
+
+	conn = i_new(struct admin_connection, 1);
+	conn->path = i_strdup(path);
+	conn->fd = net_connect_unix(path);
+	if (conn->fd == -1)
+		i_fatal("net_connect_unix(%s) failed: %m", path);
+	net_set_nonblock(conn->fd, FALSE);
+
+	conn->input = i_stream_create_fd(conn->fd, (size_t)-1, TRUE);
+	admin_send(conn, DIRECTOR_ADMIN_HANDSHAKE);
+
+	line = i_stream_read_next_line(conn->input);
+	if (line == NULL)
+		i_fatal("%s disconnected", conn->path);
+	if (!version_string_verify(line, "director-doveadm", 1)) {
+		i_fatal("%s not a compatible director-doveadm socket",
+			conn->path);
+	}
+	return conn;
+}
+
+static void admin_disconnect(struct admin_connection **_conn)
+{
+	struct admin_connection *conn = *_conn;
+
+	*_conn = NULL;
+	i_stream_destroy(&conn->input);
+	net_disconnect(conn->fd);
+	i_free(conn->path);
+	i_free(conn);
+}
+
+static void main_init(void)
+{
+	const char *admin_path;
+
+	/*set = master_service_settings_get_others(master_service)[0];
+	admin_path = t_strconcat(set->base_dir, "/director-admin", NULL);
+	admin = admin_connect(admin_path);*/
+
+	users = hash_table_create(default_pool, default_pool, 0,
+				  str_hash, (hash_cmp_callback_t *)strcmp);
+}
+
+static void main_deinit(void)
+{
+	struct hash_iterate_context *iter;
+	void *key, *value;
+
+	while (imap_clients != NULL) {
+		struct imap_client *client = imap_clients;
+
+		imap_client_destroy(&client);
+	}
+
+	while (director_connections != NULL) {
+		struct director_connection *conn = director_connections;
+
+		director_connection_destroy(&conn);
+	}
+
+	iter = hash_table_iterate_init(users);
+	while (hash_table_iterate(iter, &key, &value)) {
+		struct user *user = value;
+		user_free(user);
+	}
+	hash_table_iterate_deinit(&iter);
+	hash_table_destroy(&users);
+	//admin_disconnect(&admin);
+}
+
+int main(int argc, char *argv[])
+{
+	master_service = master_service_init("director-test", 0,
+					     &argc, &argv, NULL);
+	if (master_getopt(master_service) > 0)
+		return FATAL_DEFAULT;
+
+	master_service_init_log(master_service, "director-test: ");
+	master_service_init_finish(master_service);
+
+	main_init();
+	master_service_run(master_service, client_connected);
+	main_deinit();
+
+	master_service_deinit(&master_service);
+        return 0;
+}
--- a/src/director/director.c	Thu Jun 17 19:16:09 2010 +0100
+++ b/src/director/director.c	Thu Jun 17 19:18:34 2010 +0100
@@ -76,11 +76,17 @@
 
 int director_connect_host(struct director *dir, struct director_host *host)
 {
+	unsigned int port;
 	int fd;
 
 	i_assert(dir->right == NULL);
 
-	fd = net_connect_ip(&host->ip, host->port, &dir->self_ip);
+	if (dir->debug) {
+		i_debug("Connecting to %s:%u",
+			net_ip2addr(&host->ip), host->port);
+	}
+	port = dir->test_port != 0 ? dir->test_port : host->port;
+	fd = net_connect_ip(&host->ip, port, &dir->self_ip);
 	if (fd == -1) {
 		host->last_failed = ioloop_time;
 		i_error("connect(%s) failed: %m", host->name);
@@ -127,6 +133,8 @@
 			  "continuing delayed connections");
 		dir->ring_handshake_warning_sent = FALSE;
 	}
+	if (dir->debug)
+		i_debug("Director ring handshaked");
 
 	dir->ring_handshaked = TRUE;
 	director_set_state_changed(dir);
--- a/src/director/director.h	Thu Jun 17 19:16:09 2010 +0100
+++ b/src/director/director.h	Thu Jun 17 19:18:34 2010 +0100
@@ -27,6 +27,8 @@
 	struct ip_addr self_ip;
 	unsigned int self_port;
 
+	unsigned int test_port;
+
 	struct director_host *self_host;
 	struct director_connection *left, *right;
 
@@ -59,6 +61,7 @@
 	   director can start serving clients. */
 	unsigned int ring_handshaked:1;
 	unsigned int ring_handshake_warning_sent:1;
+	unsigned int debug:1;
 };
 
 /* Create a new director. If listen_ip specifies an actual IP, it's used with
--- a/src/director/main.c	Thu Jun 17 19:16:09 2010 +0100
+++ b/src/director/main.c	Thu Jun 17 19:18:34 2010 +0100
@@ -63,7 +63,7 @@
 	}
 
 	if (net_getunixname(conn->listen_fd, &path) < 0)
-		i_fatal("getsockname(%d) failed: %m", conn->listen_fd);
+		i_fatal("getunixname(%d) failed: %m", conn->listen_fd);
 
 	name = strrchr(path, '/');
 	if (name == NULL)
@@ -147,8 +147,6 @@
 				     set->director_mail_servers) < 0)
 		i_fatal("Invalid value for director_mail_servers setting");
 	director->orig_config_hosts = mail_hosts_dup(director->mail_hosts);
-
-	director_connect(director);
 }
 
 static void main_deinit(void)
@@ -168,11 +166,26 @@
 		&director_setting_parser_info,
 		NULL
 	};
+	unsigned int test_port;
 	const char *error;
+	bool debug = FALSE;
+	int c;
 
-	master_service = master_service_init("director", 0, &argc, &argv, NULL);
-	if (master_getopt(master_service) > 0)
-		return FATAL_DEFAULT;
+	master_service = master_service_init("director", 0, &argc, &argv,
+					     "Dt:");
+	while ((c = master_getopt(master_service)) > 0) {
+		switch (c) {
+		case 'D':
+			debug = TRUE;
+			break;
+		case 't':
+			if (str_to_uint(optarg, &test_port) < 0)
+				i_fatal("-t: Not a number: %s", optarg);
+			break;
+		default:
+			return FATAL_DEFAULT;
+		}
+	}
 	if (master_service_settings_read_simple(master_service, set_roots,
 						&error) < 0)
 		i_fatal("Error reading configuration: %s", error);
@@ -184,6 +197,18 @@
 	master_service_init_finish(master_service);
 
 	main_init();
+	director->test_port = test_port;
+	director->debug = debug;
+	director_connect(director);
+
+	if (director->test_port != 0) {
+		/* we're testing, possibly writing to same log file.
+		   make it clear which director we are. */
+		master_service_init_log(master_service,
+			t_strdup_printf("director(%s): ",
+					net_ip2addr(&director->self_ip)));
+	}
+
 	master_service_run(master_service, client_connected);
 	main_deinit();