diff --git a/Makefile.am b/Makefile.am index fa327d6c..2268de88 100644 --- a/Makefile.am +++ b/Makefile.am @@ -182,7 +182,7 @@ dist_tmate_SOURCES = \ tmate-encoder.c \ tmate-decoder.c \ tmate-msg.c \ - tmate.c \ + tmate-session.c \ tmux.c \ tty-acs.c \ tty-keys.c \ diff --git a/configure.ac b/configure.ac index 20914916..a86799f0 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # $Id$ # Miscellaneous autofoo bullshit. -AC_INIT(tmate, 1.8.4) +AC_INIT(tmate, 1.8.6) AC_CONFIG_AUX_DIR(etc) AM_INIT_AUTOMAKE([foreign]) diff --git a/server.c b/server.c index 6a636825..c99ce8e4 100644 --- a/server.c +++ b/server.c @@ -187,7 +187,7 @@ server_start(int lockfd, char *lockfile) } } - tmate_client_start(); + tmate_session_start(); cmdq_continue(cfg_cmd_q); diff --git a/tmate-debug.c b/tmate-debug.c index c480e658..15dd31a6 100644 --- a/tmate-debug.c +++ b/tmate-debug.c @@ -73,3 +73,17 @@ void tmate_print_trace(void) free (strings); } + + +static void handle_sigsegv(int sig) +{ + /* TODO send stack trace to server */ + tmate_info("CRASH, printing stack trace"); + tmate_print_trace(); + tmate_fatal("CRASHED"); +} + +void tmate_catch_sigsegv(void) +{ + signal(SIGSEGV, handle_sigsegv); +} diff --git a/tmate-encoder.c b/tmate-encoder.c index 6ac9a590..e4a95a66 100644 --- a/tmate-encoder.c +++ b/tmate-encoder.c @@ -1,5 +1,7 @@ #include "tmate.h" +#define DEFAULT_ENCODER (&tmate_session.encoder) + static int msgpack_write(void *data, const char *buf, unsigned int len) { struct tmate_encoder *encoder = data; @@ -26,13 +28,14 @@ void tmate_encoder_init(struct tmate_encoder *encoder) msgpack_pack_raw_body(pk, str, __strlen); \ } while(0) -#define pack(what, ...) msgpack_pack_##what(&tmate_encoder->pk, __VA_ARGS__) +#define pack(what, ...) msgpack_pack_##what(&DEFAULT_ENCODER->pk, __VA_ARGS__) void tmate_write_header(void) { - pack(array, 2); + pack(array, 3); pack(int, TMATE_HEADER); pack(int, TMATE_PROTOCOL_VERSION); + pack(string, VERSION); } void tmate_sync_layout(void) diff --git a/tmate-session.c b/tmate-session.c new file mode 100644 index 00000000..93f82955 --- /dev/null +++ b/tmate-session.c @@ -0,0 +1,107 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "tmate.h" + +#define TMATE_DNS_RETRY_TIMEOUT 10 + +struct tmate_session tmate_session; + +static struct evdns_base *ev_dnsbase; +static struct event ev_dns_retry; +static void lookup_and_connect(void); + +static void on_dns_retry(evutil_socket_t fd, short what, void *arg) +{ + lookup_and_connect(); +} + +static void dns_cb(int errcode, struct evutil_addrinfo *addr, void *ptr) +{ + struct tmate_ssh_client *client; + struct evutil_addrinfo *ai; + struct timeval tv; + + if (errcode) { + tmate_status_message("%s lookup failure. Retrying in %d seconds (%s)", + TMATE_HOST, TMATE_DNS_RETRY_TIMEOUT, + evutil_gai_strerror(errcode)); + + tv.tv_sec = TMATE_DNS_RETRY_TIMEOUT; + tv.tv_usec = 0; + + evtimer_assign(&ev_dns_retry, ev_base, on_dns_retry, NULL); + evtimer_add(&ev_dns_retry, &tv); + + return; + } + + tmate_status_message("Connecting to %s...", TMATE_HOST); + + for (ai = addr; ai; ai = ai->ai_next) { + char buf[128]; + const char *ip = NULL; + if (ai->ai_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; + ip = evutil_inet_ntop(AF_INET, &sin->sin_addr, buf, 128); + } else if (ai->ai_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; + ip = evutil_inet_ntop(AF_INET6, &sin6->sin6_addr, buf, 128); + } + + tmate_debug("Trying server %s", ip); + + /* + * Note: We don't deal with the client list. Clients manage it + * and free client structs when necessary. + */ + (void)tmate_ssh_client_alloc(&tmate_session, ip); + } + + evutil_freeaddrinfo(addr); + + evdns_base_free(ev_dnsbase, 0); + ev_dnsbase = NULL; +} + +static void lookup_and_connect(void) +{ + struct evutil_addrinfo hints; + + if (!ev_dnsbase) + ev_dnsbase = evdns_base_new(ev_base, 1); + if (!ev_dnsbase) + tmate_fatal("Cannot initialize the DNS lookup service"); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = 0; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + tmate_status_message("Looking up %s...", TMATE_HOST); + (void)evdns_getaddrinfo(ev_dnsbase, TMATE_HOST, NULL, + &hints, dns_cb, NULL); +} + +void tmate_session_start(void) +{ + tmate_catch_sigsegv(); + + TAILQ_INIT(&tmate_session.clients); + tmate_encoder_init(&tmate_session.encoder); + tmate_decoder_init(&tmate_session.decoder); + + lookup_and_connect(); + + /* The header will be written as soon as the first client connects */ + tmate_write_header(); +} diff --git a/tmate-ssh-client.c b/tmate-ssh-client.c index f4e55e27..2429391f 100644 --- a/tmate-ssh-client.c +++ b/tmate-ssh-client.c @@ -1,9 +1,8 @@ -#include -#include #include #include #include #include +#include #include "tmate.h" @@ -19,14 +18,10 @@ static void printflike2 reconnect_session(struct tmate_ssh_client *client, static void log_function(ssh_session session, int priority, const char *message, void *userdata) { - tmate_debug("[%d] %s", priority, message); + struct tmate_ssh_client *client = userdata; + tmate_debug("[%s] [%d] %s", client->server_ip, priority, message); } -static struct ssh_callbacks_struct ssh_session_callbacks = { - .log_function = log_function -}; - - static void register_session_fd_event(struct tmate_ssh_client *client) { if (!event_initialized(&client->ev_ssh)) { @@ -42,20 +37,24 @@ static void register_session_fd_event(struct tmate_ssh_client *client) static void register_input_stream_event(struct tmate_ssh_client *client) { - if (!event_initialized(&client->encoder->ev_readable)) { - event_assign(&client->encoder->ev_readable, ev_base, -1, + struct tmate_encoder *encoder = &client->tmate_session->encoder; + + if (!event_initialized(&encoder->ev_readable)) { + event_assign(&encoder->ev_readable, ev_base, -1, EV_READ | EV_PERSIST, __flush_input_stream, client); - event_add(&client->encoder->ev_readable, NULL); + event_add(&encoder->ev_readable, NULL); + client->has_encoder = 1; } } static void consume_channel(struct tmate_ssh_client *client) { + struct tmate_decoder *decoder = &client->tmate_session->decoder; char *buf; ssize_t len; for (;;) { - tmate_decoder_get_buffer(client->decoder, &buf, &len); + tmate_decoder_get_buffer(decoder, &buf, &len); len = ssh_channel_read_nonblocking(client->channel, buf, len, 0); if (len < 0) { reconnect_session(client, "Error reading from channel: %s", @@ -66,7 +65,21 @@ static void consume_channel(struct tmate_ssh_client *client) if (len == 0) break; - tmate_decoder_commit(client->decoder, len); + tmate_decoder_commit(decoder, len); + } +} + +static void connection_complete(struct tmate_ssh_client *connected_client) +{ + struct tmate_session *session = connected_client->tmate_session; + struct tmate_ssh_client *client, *tmp_client; + + TAILQ_FOREACH_SAFE(client, &session->clients, node, tmp_client) { + if (client == connected_client) + continue; + + assert(!client->has_encoder); + tmate_ssh_client_free(client); } } @@ -93,7 +106,7 @@ static void on_session_event(struct tmate_ssh_client *client) return; } - ssh_set_callbacks(session, &ssh_session_callbacks); + ssh_set_callbacks(session, &client->ssh_callbacks); client->channel = channel = ssh_channel_new(session); if (!channel) { @@ -102,13 +115,12 @@ static void on_session_event(struct tmate_ssh_client *client) } ssh_set_blocking(session, 0); - ssh_options_set(session, SSH_OPTIONS_HOST, TMATE_HOST); + ssh_options_set(session, SSH_OPTIONS_HOST, client->server_ip); ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); ssh_options_set(session, SSH_OPTIONS_PORT, &port); ssh_options_set(session, SSH_OPTIONS_USER, "tmate"); ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); - tmate_status_message("Connecting to %s...", TMATE_HOST); client->state = SSH_CONNECT; /* fall through */ @@ -123,14 +135,14 @@ static void on_session_event(struct tmate_ssh_client *client) return; case SSH_OK: register_session_fd_event(client); - tmate_debug("Connected"); + tmate_debug("Establishing connection to %s", client->server_ip); client->state = SSH_AUTH_SERVER; /* fall through */ } case SSH_AUTH_SERVER: if ((hash_len = ssh_get_pubkey_hash(session, &hash)) < 0) { - disconnect_session(client, "Cannnot authenticate server"); + disconnect_session(client, "Cannot authenticate server"); return; } @@ -165,11 +177,20 @@ static void on_session_event(struct tmate_ssh_client *client) free(hash_str); if (!match) { - disconnect_session(client, "Cannnot authenticate server"); + disconnect_session(client, "Cannot authenticate server"); return; } + /* + * At this point, we abort other connection attempts to the + * other tmate servers, since we have reached the fastest one. + * We need to do it before we ask the user its passphrase, + * otherwise the speed test would be biased. + */ + tmate_debug("Connected to %s", client->server_ip); + connection_complete(client); client->state = SSH_AUTH_CLIENT; + /* fall through */ case SSH_AUTH_CLIENT: @@ -179,7 +200,8 @@ static void on_session_event(struct tmate_ssh_client *client) case SSH_AUTH_PARTIAL: case SSH_AUTH_INFO: case SSH_AUTH_DENIED: - disconnect_session(client, "Access denied. Check your SSH keys."); + disconnect_session(client, "Access denied. Check your SSH keys " + "(passphrases are not supported yet)."); return; case SSH_AUTH_ERROR: reconnect_session(client, "Auth error: %s", @@ -236,7 +258,8 @@ static void on_session_event(struct tmate_ssh_client *client) static void flush_input_stream(struct tmate_ssh_client *client) { - struct evbuffer *evb = client->encoder->buffer; + struct tmate_encoder *encoder = &client->tmate_session->encoder; + struct evbuffer *evb = encoder->buffer; ssize_t len, written; char *buf; @@ -274,16 +297,23 @@ static void __on_session_event(evutil_socket_t fd, short what, void *arg) static void __disconnect_session(struct tmate_ssh_client *client, const char *fmt, va_list va) { - __tmate_status_message(fmt, va); + struct tmate_encoder *encoder; + + if (fmt) + __tmate_status_message(fmt, va); + else + tmate_debug("Disconnecting %s", client->server_ip); if (event_initialized(&client->ev_ssh)) { event_del(&client->ev_ssh); client->ev_ssh.ev_flags = 0; } - if (event_initialized(&client->encoder->ev_readable)) { - event_del(&client->encoder->ev_readable); - client->encoder->ev_readable.ev_flags = 0; + if (client->has_encoder) { + encoder = &client->tmate_session->encoder; + event_del(&encoder->ev_readable); + encoder->ev_readable.ev_flags = 0; + client->has_encoder = 0; } if (client->session) { @@ -337,21 +367,38 @@ static void printflike2 reconnect_session(struct tmate_ssh_client *client, #endif } -void tmate_ssh_client_init(struct tmate_ssh_client *client, - struct tmate_encoder *encoder, - struct tmate_decoder *decoder) -{ - ssh_callbacks_init(&ssh_session_callbacks); +struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session, + const char *server_ip) +{ + struct tmate_ssh_client *client; + client = xmalloc(sizeof(*client)); + + ssh_callbacks_init(&client->ssh_callbacks); + client->ssh_callbacks.log_function = log_function; + client->ssh_callbacks.userdata = client; + + client->tmate_session = session; + TAILQ_INSERT_TAIL(&session->clients, client, node); + + client->server_ip = xstrdup(server_ip); client->state = SSH_NONE; client->session = NULL; client->channel = NULL; - - client->encoder = encoder; - client->decoder = decoder; + client->has_encoder = 0; evtimer_assign(&client->ev_ssh_reconnect, ev_base, on_reconnect_timer, client); connect_session(client); + + return client; +} + +void tmate_ssh_client_free(struct tmate_ssh_client *client) +{ + disconnect_session(client, NULL); + TAILQ_REMOVE(&client->tmate_session->clients, client, node); + free(client->server_ip); + free(client); } diff --git a/tmate.c b/tmate.c deleted file mode 100644 index c670d39c..00000000 --- a/tmate.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "tmate.h" - -struct tmate_encoder *tmate_encoder; - -static struct tmate_ssh_client client; -static struct tmate_encoder encoder; -static struct tmate_decoder decoder; - -void tmate_client_start(void) -{ - tmate_encoder_init(&encoder); - tmate_decoder_init(&decoder); - tmate_encoder = &encoder; - - tmate_ssh_client_init(&client, &encoder, &decoder); - - tmate_write_header(); -} diff --git a/tmate.h b/tmate.h index 57d5bcc5..7969dbfe 100644 --- a/tmate.h +++ b/tmate.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "tmux.h" @@ -17,7 +18,7 @@ #define TMATE_MAX_MESSAGE_SIZE (16*1024) -#define TMATE_PROTOCOL_VERSION 2 +#define TMATE_PROTOCOL_VERSION 3 enum tmate_commands { TMATE_HEADER, @@ -72,7 +73,7 @@ extern void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len); /* tmate-ssh-client.c */ #ifdef DEVENV -#define TMATE_HOST "127.0.0.1" +#define TMATE_HOST "localhost" #define TMATE_PORT 2200 #else #define TMATE_HOST "master.tmate.io" @@ -94,28 +95,60 @@ enum tmate_ssh_client_state_types { }; struct tmate_ssh_client { + /* XXX The "session" word is used for three things: + * - the ssh session + * - the tmate sesssion + * - the tmux session + * A tmux session is associated 1:1 with a tmate session. + * An ssh session belongs to a tmate session, and a tmate session + * has one ssh session, except during bootstrapping where + * there is one ssh session per tmate server, and the first one wins. + */ + struct tmate_session *tmate_session; + TAILQ_ENTRY(tmate_ssh_client) node; + + char *server_ip; + + int has_encoder; int state; + + /* + * ssh_callbacks is allocated because the libssh API sucks (userdata + * has to be in the struct itself). + */ + struct ssh_callbacks_struct ssh_callbacks; ssh_session session; ssh_channel channel; - struct tmate_encoder *encoder; - struct tmate_decoder *decoder; - struct event ev_ssh; struct event ev_ssh_reconnect; }; +TAILQ_HEAD(tmate_ssh_clients, tmate_ssh_client); -extern void tmate_ssh_client_init(struct tmate_ssh_client *client, - struct tmate_encoder *encoder, - struct tmate_decoder *decoder); +extern struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session, + const char *server_ip); +extern void tmate_ssh_client_free(struct tmate_ssh_client *client); -/* tmate.c */ +/* tmate-session.c */ -extern struct tmate_encoder *tmate_encoder; -extern void tmate_client_start(void); +struct tmate_session { + struct tmate_encoder encoder; + struct tmate_decoder decoder; + + /* + * This list contains one connection per IP. The first connected + * client wins, and saved in *client. When we have a winner, the + * losers are disconnected and killed. + */ + struct tmate_ssh_clients clients; +}; + +extern struct tmate_session tmate_session; +extern void tmate_session_start(void); /* tmate-debug.c */ -extern void tmate_print_trace (void); +extern void tmate_print_trace(void); +extern void tmate_catch_sigsegv(void); /* tmate-msg.c */