Mercurial > dovecot > core-2.2
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();