Reconnect wip

This commit is contained in:
Nicolas Viennot 2016-03-26 19:00:16 -04:00
parent cc20e826e0
commit cdfb6d7ef1
7 changed files with 259 additions and 74 deletions

View File

@ -114,19 +114,29 @@ out:
free(cmd_str); free(cmd_str);
} }
static void handle_set_env(__unused struct tmate_session *session, static void maybe_save_reconnection_data(struct tmate_session *session,
const char *name, const char *value)
{
if (!strcmp(name, "tmate_reconnection_data")) {
free(session->reconnection_data);
session->reconnection_data = xstrdup(value);
}
}
static void handle_set_env(struct tmate_session *session,
struct tmate_unpacker *uk) struct tmate_unpacker *uk)
{ {
char *name = unpack_string(uk); char *name = unpack_string(uk);
char *value = unpack_string(uk); char *value = unpack_string(uk);
tmate_set_env(name, value); tmate_set_env(name, value);
maybe_save_reconnection_data(session, name, value);
free(name); free(name);
free(value); free(value);
} }
static void handle_ready(__unused struct tmate_session *session, static void handle_ready(struct tmate_session *session,
__unused struct tmate_unpacker *uk) __unused struct tmate_unpacker *uk)
{ {
session->tmate_env_ready = 1; session->tmate_env_ready = 1;

View File

@ -236,3 +236,126 @@ void tmate_write_fin(void)
pack(array, 1); pack(array, 1);
pack(int, TMATE_OUT_FIN); pack(int, TMATE_OUT_FIN);
} }
static void do_snapshot(unsigned int max_history_lines,
struct window_pane *pane)
{
struct screen *screen;
struct grid *grid;
struct grid_line *line;
struct grid_cell gc;
unsigned int line_i, i;
unsigned int max_lines;
size_t str_len;
screen = &pane->base;
grid = screen->grid;
pack(array, 4);
pack(int, pane->id);
pack(array, 2);
pack(int, screen->cx);
pack(int, screen->cy);
pack(unsigned_int, screen->mode);
max_lines = max_history_lines + grid->sy;
#define grid_num_lines(grid) (grid->hsize + grid->sy)
if (grid_num_lines(grid) > max_lines)
line_i = grid_num_lines(grid) - max_lines;
else
line_i = 0;
pack(array, grid_num_lines(grid) - line_i);
for (; line_i < grid_num_lines(grid); line_i++) {
line = &grid->linedata[line_i];
pack(array, 2);
str_len = 0;
for (i = 0; i < line->cellsize; i++) {
grid_get_cell(grid, i, line_i, &gc);
str_len += gc.data.size;
}
pack(str, str_len);
for (i = 0; i < line->cellsize; i++) {
grid_get_cell(grid, i, line_i, &gc);
pack(str_body, gc.data.data, gc.data.size);
}
pack(array, line->cellsize);
for (i = 0; i < line->cellsize; i++) {
grid_get_cell(grid, i, line_i, &gc);
pack(unsigned_int, ((gc.flags << 24) |
(gc.attr << 16) |
(gc.bg << 8) |
gc.fg ));
}
}
}
static void tmate_send_session_snapshot(unsigned int max_history_lines)
{
struct session *s;
struct winlink *wl;
struct window *w;
struct window_pane *pane;
int num_panes;
pack(array, 2);
pack(int, TMATE_OUT_SNAPSHOT);
s = RB_MIN(sessions, &sessions);
if (!s)
tmate_fatal("no session?");
num_panes = 0;
RB_FOREACH(wl, winlinks, &s->windows) {
w = wl->window;
if (!w)
continue;
TAILQ_FOREACH(pane, &w->panes, entry)
num_panes++;
}
pack(array, num_panes);
RB_FOREACH(wl, winlinks, &s->windows) {
w = wl->window;
if (!w)
continue;
TAILQ_FOREACH(pane, &w->panes, entry)
do_snapshot(max_history_lines, pane);
}
}
static void tmate_send_reconnection_data(struct tmate_session *session)
{
if (!session->reconnection_data)
return;
pack(array, 2);
pack(int, TMATE_OUT_RECONNECT);
pack(string, session->reconnection_data);
}
#define RECONNECTION_MAX_HISTORY_LINE 300
void tmate_send_reconnection_state(struct tmate_session *session)
{
/* Start with a fresh encoder */
tmate_encoder_destroy(&session->encoder);
tmate_encoder_init(&session->encoder, NULL, session);
tmate_write_header();
tmate_send_reconnection_data(session);
/* TODO send all option variables */
tmate_write_ready();
tmate_sync_layout();
tmate_send_session_snapshot(RECONNECTION_MAX_HISTORY_LINE);
}

View File

@ -65,13 +65,22 @@ void tmate_encoder_init(struct tmate_encoder *encoder,
encoder->ev_active = false; encoder->ev_active = false;
} }
void tmate_encoder_destroy(struct tmate_encoder *encoder)
{
/* encoder->pk doesn't need any cleanup */
evbuffer_free(encoder->buffer);
event_del(&encoder->ev_buffer);
memset(encoder, 0, sizeof(*encoder));
}
void tmate_encoder_set_ready_callback(struct tmate_encoder *encoder, void tmate_encoder_set_ready_callback(struct tmate_encoder *encoder,
tmate_encoder_write_cb *callback, tmate_encoder_write_cb *callback,
void *userdata) void *userdata)
{ {
encoder->ready_callback = callback; encoder->ready_callback = callback;
encoder->userdata = userdata; encoder->userdata = userdata;
encoder->ready_callback(encoder->userdata, encoder->buffer); if (encoder->ready_callback)
encoder->ready_callback(encoder->userdata, encoder->buffer);
} }
void tmate_decoder_error(void) void tmate_decoder_error(void)
@ -178,6 +187,12 @@ void tmate_decoder_init(struct tmate_decoder *decoder, tmate_decoder_reader *rea
decoder->userdata = userdata; decoder->userdata = userdata;
} }
void tmate_decoder_destroy(struct tmate_decoder *decoder)
{
msgpack_unpacker_destroy(&decoder->unpacker);
memset(decoder, 0, sizeof(*decoder));
}
void tmate_decoder_get_buffer(struct tmate_decoder *decoder, void tmate_decoder_get_buffer(struct tmate_decoder *decoder,
char **buf, size_t *len) char **buf, size_t *len)
{ {

View File

@ -29,6 +29,7 @@ enum tmate_control_in_msg_types {
TMATE_CTL_PANE_KEYS, TMATE_CTL_PANE_KEYS,
TMATE_CTL_RESIZE, TMATE_CTL_RESIZE,
TMATE_CTL_EXEC_RESPONSE, TMATE_CTL_EXEC_RESPONSE,
TMATE_CTL_RENAME_SESSION,
}; };
/* /*
@ -37,6 +38,7 @@ enum tmate_control_in_msg_types {
[TMATE_CTL_PANE_KEYS, int: pane_id, string: keys] [TMATE_CTL_PANE_KEYS, int: pane_id, string: keys]
[TMATE_CTL_RESIZE, int: sx, int: sy] // sx == -1: no clients [TMATE_CTL_RESIZE, int: sx, int: sy] // sx == -1: no clients
[TMATE_CTL_EXEC_RESPONSE, int: exit_code, string: message] [TMATE_CTL_EXEC_RESPONSE, int: exit_code, string: message]
[TMATE_CTL_RENAME_SESSION, string: stoken, string: stoken_ro]
*/ */
enum tmate_daemon_out_msg_types { enum tmate_daemon_out_msg_types {
@ -50,6 +52,8 @@ enum tmate_daemon_out_msg_types {
TMATE_OUT_WRITE_COPY_MODE, TMATE_OUT_WRITE_COPY_MODE,
TMATE_OUT_FIN, TMATE_OUT_FIN,
TMATE_OUT_READY, TMATE_OUT_READY,
TMATE_OUT_RECONNECT,
TMATE_OUT_SNAPSHOT,
}; };
/* /*

View File

@ -11,7 +11,8 @@
#include "tmate.h" #include "tmate.h"
#define TMATE_DNS_RETRY_TIMEOUT 10 #define TMATE_DNS_RETRY_TIMEOUT 2
#define TMATE_RECONNECT_RETRY_TIMEOUT 2
struct tmate_session tmate_session; struct tmate_session tmate_session;
@ -129,7 +130,8 @@ void tmate_session_init(struct event_base *base)
void tmate_session_start(void) void tmate_session_start(void)
{ {
/* We split init and start because: /*
* We split init and start because:
* - We need to process the tmux config file during the connection as * - We need to process the tmux config file during the connection as
* we are setting up the tmate identity. * we are setting up the tmate identity.
* - While we are parsing the config file, we need to be able to * - While we are parsing the config file, we need to be able to
@ -137,3 +139,43 @@ void tmate_session_start(void)
*/ */
lookup_and_connect(); lookup_and_connect();
} }
static void on_reconnect_retry(__unused evutil_socket_t fd, __unused short what, void *arg)
{
struct tmate_session *session = arg;
if (session->last_server_ip) {
/*
* We have a previous server ip. Let's try that again first,
* but then connect to any server if it fails again.
*/
(void)tmate_ssh_client_alloc(&tmate_session, session->last_server_ip);
free(session->last_server_ip);
session->last_server_ip = NULL;
} else {
lookup_and_connect();
}
}
void tmate_reconnect_session(struct tmate_session *session)
{
/*
* We no longer have an SSH connection. Time to reconnect.
* We'll reuse some of the session information if we can,
* and we'll try to reconnect to the same server if possible,
* to avoid an SSH connection string change.
*/
struct timeval tv = { .tv_sec = TMATE_RECONNECT_RETRY_TIMEOUT, .tv_usec = 0 };
evtimer_assign(&session->ev_connection_retry, session->ev_base,
on_reconnect_retry, session);
evtimer_add(&session->ev_connection_retry, &tv);
tmate_status_message("Reconnecting...");
/*
* This says that we'll need to send a snapshot of the current state.
* Until we have persisted logs...
*/
session->reconnected = true;
}

View File

@ -12,7 +12,7 @@ static void __on_ssh_client_event(evutil_socket_t fd, short what, void *arg);
static void printflike(2, 3) kill_ssh_client(struct tmate_ssh_client *client, static void printflike(2, 3) kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...); const char *fmt, ...);
static void printflike(2, 3) reconnect_ssh_client(struct tmate_ssh_client *client, static void printflike(2, 3) kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...); const char *fmt, ...);
static void read_channel(struct tmate_ssh_client *client) static void read_channel(struct tmate_ssh_client *client)
@ -25,8 +25,8 @@ static void read_channel(struct tmate_ssh_client *client)
tmate_decoder_get_buffer(decoder, &buf, &len); tmate_decoder_get_buffer(decoder, &buf, &len);
len = ssh_channel_read_nonblocking(client->channel, buf, len, 0); len = ssh_channel_read_nonblocking(client->channel, buf, len, 0);
if (len < 0) { if (len < 0) {
reconnect_ssh_client(client, "Error reading from channel: %s", kill_ssh_client(client, "Error reading from channel: %s",
ssh_get_error(client->session)); ssh_get_error(client->session));
break; break;
} }
@ -61,8 +61,8 @@ static void on_encoder_write(void *userdata, struct evbuffer *buffer)
written = ssh_channel_write(client->channel, buf, len); written = ssh_channel_write(client->channel, buf, len);
if (written < 0) { if (written < 0) {
reconnect_ssh_client(client, "Error writing to channel: %s", kill_ssh_client(client, "Error writing to channel: %s",
ssh_get_error(client->session)); ssh_get_error(client->session));
break; break;
} }
@ -245,8 +245,8 @@ static void on_ssh_client_event(struct tmate_ssh_client *client)
init_conn_fd(client); init_conn_fd(client);
return; return;
case SSH_ERROR: case SSH_ERROR:
reconnect_ssh_client(client, "Error connecting: %s", kill_ssh_client(client, "Error connecting: %s",
ssh_get_error(session)); ssh_get_error(session));
return; return;
case SSH_OK: case SSH_OK:
init_conn_fd(client); init_conn_fd(client);
@ -315,19 +315,20 @@ static void on_ssh_client_event(struct tmate_ssh_client *client)
case SSH_AUTH_PARTIAL: case SSH_AUTH_PARTIAL:
case SSH_AUTH_INFO: case SSH_AUTH_INFO:
case SSH_AUTH_DENIED: case SSH_AUTH_DENIED:
if (client->tmate_session->need_passphrase) if (client->tmate_session->need_passphrase) {
request_passphrase(client); request_passphrase(client);
else } else {
kill_ssh_client(client, "SSH keys not found." kill_ssh_client(client, "SSH keys not found."
" Run 'ssh-keygen' to create keys and try again."); " Run 'ssh-keygen' to create keys and try again.");
return;
}
if (client->tried_passphrase) if (client->tried_passphrase)
tmate_status_message("Can't load SSH key." tmate_status_message("Can't load SSH key."
" Try typing passphrase again in case of typo. ctrl-c to abort."); " Try typing passphrase again in case of typo. ctrl-c to abort.");
return; return;
case SSH_AUTH_ERROR: case SSH_AUTH_ERROR:
reconnect_ssh_client(client, "Auth error: %s", kill_ssh_client(client, "Auth error: %s", ssh_get_error(session));
ssh_get_error(session));
return; return;
case SSH_AUTH_SUCCESS: case SSH_AUTH_SUCCESS:
tmate_debug("Auth successful"); tmate_debug("Auth successful");
@ -340,8 +341,8 @@ static void on_ssh_client_event(struct tmate_ssh_client *client)
case SSH_AGAIN: case SSH_AGAIN:
return; return;
case SSH_ERROR: case SSH_ERROR:
reconnect_ssh_client(client, "Error opening channel: %s", kill_ssh_client(client, "Error opening channel: %s",
ssh_get_error(session)); ssh_get_error(session));
return; return;
case SSH_OK: case SSH_OK:
tmate_debug("Session opened, initalizing tmate"); tmate_debug("Session opened, initalizing tmate");
@ -354,8 +355,8 @@ static void on_ssh_client_event(struct tmate_ssh_client *client)
case SSH_AGAIN: case SSH_AGAIN:
return; return;
case SSH_ERROR: case SSH_ERROR:
reconnect_ssh_client(client, "Error initializing tmate: %s", kill_ssh_client(client, "Error initializing tmate: %s",
ssh_get_error(session)); ssh_get_error(session));
return; return;
case SSH_OK: case SSH_OK:
tmate_debug("Ready"); tmate_debug("Ready");
@ -365,19 +366,22 @@ static void on_ssh_client_event(struct tmate_ssh_client *client)
client->state = SSH_READY; client->state = SSH_READY;
if (client->tmate_session->reconnected)
tmate_send_reconnection_state(client->tmate_session);
tmate_encoder_set_ready_callback(&client->tmate_session->encoder, tmate_encoder_set_ready_callback(&client->tmate_session->encoder,
on_encoder_write, client); on_encoder_write, client);
tmate_decoder_init(&client->tmate_session->decoder, tmate_decoder_init(&client->tmate_session->decoder,
on_decoder_read, client); on_decoder_read, client);
free(client->tmate_session->last_server_ip);
client->tmate_session->last_server_ip = xstrdup(client->server_ip);
/* fall through */ /* fall through */
} }
case SSH_READY: case SSH_READY:
read_channel(client); read_channel(client);
if (!ssh_is_connected(session)) {
reconnect_ssh_client(client, "Disconnected");
return;
}
} }
} }
@ -386,19 +390,37 @@ static void __on_ssh_client_event(__unused evutil_socket_t fd, __unused short wh
on_ssh_client_event(arg); on_ssh_client_event(arg);
} }
static void __kill_ssh_client(struct tmate_ssh_client *client, static void kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, va_list va) const char *fmt, ...)
{ {
if (fmt && TAILQ_EMPTY(&client->tmate_session->clients)) bool last_client;
__tmate_status_message(fmt, va); va_list ap;
else
tmate_debug("Disconnecting %s", client->server_ip); TAILQ_REMOVE(&client->tmate_session->clients, client, node);
last_client = TAILQ_EMPTY(&client->tmate_session->clients);
if (fmt && last_client) {
va_start(ap, fmt);
__tmate_status_message(fmt, ap);
va_end(ap);
}
tmate_debug("SSH client killed (%s)", client->server_ip);
if (client->has_init_conn_fd) { if (client->has_init_conn_fd) {
event_del(&client->ev_ssh); event_del(&client->ev_ssh);
client->has_init_conn_fd = false; client->has_init_conn_fd = false;
} }
if (client->state == SSH_READY) {
tmate_encoder_set_ready_callback(&client->tmate_session->encoder, NULL, NULL);
tmate_decoder_destroy(&client->tmate_session->decoder);
client->tmate_session->min_sx = -1;
client->tmate_session->min_sy = -1;
recalculate_sizes();
}
if (client->session) { if (client->session) {
/* ssh_free() also frees the associated channels. */ /* ssh_free() also frees the associated channels. */
ssh_free(client->session); ssh_free(client->session);
@ -406,19 +428,8 @@ static void __kill_ssh_client(struct tmate_ssh_client *client,
client->channel = NULL; client->channel = NULL;
} }
client->state = SSH_NONE; if (last_client)
} tmate_reconnect_session(client->tmate_session);
static void kill_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...)
{
va_list ap;
TAILQ_REMOVE(&client->tmate_session->clients, client, node);
va_start(ap, fmt);
__kill_ssh_client(client, fmt, ap);
va_end(ap);
free(client->server_ip); free(client->server_ip);
free(client); free(client);
@ -432,33 +443,6 @@ static void connect_ssh_client(struct tmate_ssh_client *client)
} }
} }
static void on_reconnect_timer(__unused evutil_socket_t fd, __unused short what, void *arg)
{
connect_ssh_client(arg);
}
static void reconnect_ssh_client(struct tmate_ssh_client *client,
const char *fmt, ...)
{
/* struct timeval tv; */
va_list ap;
#if 1
TAILQ_REMOVE(&client->tmate_session->clients, client, node);
#endif
va_start(ap, fmt);
__kill_ssh_client(client, fmt, ap);
va_end(ap);
/* Not yet implemented... */
#if 0
tv.tv_sec = 1;
tv.tv_usec = 0;
evtimer_add(&client->ev_ssh_reconnect, &tv);
#endif
}
static void ssh_log_function(int priority, const char *function, static void ssh_log_function(int priority, const char *function,
const char *buffer, __unused void *userdata) const char *buffer, __unused void *userdata)
{ {
@ -489,9 +473,6 @@ struct tmate_ssh_client *tmate_ssh_client_alloc(struct tmate_session *session,
client->has_init_conn_fd = false; client->has_init_conn_fd = false;
evtimer_assign(&client->ev_ssh_reconnect, session->ev_base,
on_reconnect_timer, client);
connect_ssh_client(client); connect_ssh_client(client);
return client; return client;

12
tmate.h
View File

@ -30,6 +30,7 @@ struct tmate_encoder {
extern void tmate_encoder_init(struct tmate_encoder *encoder, extern void tmate_encoder_init(struct tmate_encoder *encoder,
tmate_encoder_write_cb *callback, tmate_encoder_write_cb *callback,
void *userdata); void *userdata);
extern void tmate_encoder_destroy(struct tmate_encoder *encoder);
extern void tmate_encoder_set_ready_callback(struct tmate_encoder *encoder, extern void tmate_encoder_set_ready_callback(struct tmate_encoder *encoder,
tmate_encoder_write_cb *callback, tmate_encoder_write_cb *callback,
void *userdata); void *userdata);
@ -50,6 +51,7 @@ struct tmate_decoder {
}; };
extern void tmate_decoder_init(struct tmate_decoder *decoder, tmate_decoder_reader *reader, void *userdata); extern void tmate_decoder_init(struct tmate_decoder *decoder, tmate_decoder_reader *reader, void *userdata);
extern void tmate_decoder_destroy(struct tmate_decoder *decoder);
extern void tmate_decoder_get_buffer(struct tmate_decoder *decoder, char **buf, size_t *len); extern void tmate_decoder_get_buffer(struct tmate_decoder *decoder, char **buf, size_t *len);
extern void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len); extern void tmate_decoder_commit(struct tmate_decoder *decoder, size_t len);
@ -75,6 +77,8 @@ extern void unpack_array(struct tmate_unpacker *uk, struct tmate_unpacker *neste
#define TMATE_PROTOCOL_VERSION 6 #define TMATE_PROTOCOL_VERSION 6
struct tmate_session;
extern void tmate_write_header(void); extern void tmate_write_header(void);
extern void tmate_write_ready(void); extern void tmate_write_ready(void);
extern void tmate_sync_layout(void); extern void tmate_sync_layout(void);
@ -86,6 +90,7 @@ extern void tmate_status(const char *left, const char *right);
extern void tmate_sync_copy_mode(struct window_pane *wp); extern void tmate_sync_copy_mode(struct window_pane *wp);
extern void tmate_write_copy_mode(struct window_pane *wp, const char *str); extern void tmate_write_copy_mode(struct window_pane *wp, const char *str);
extern void tmate_write_fin(void); extern void tmate_write_fin(void);
extern void tmate_send_reconnection_state(struct tmate_session *session);
/* tmate-decoder.c */ /* tmate-decoder.c */
@ -135,7 +140,6 @@ struct tmate_ssh_client {
bool has_init_conn_fd; bool has_init_conn_fd;
struct event ev_ssh; struct event ev_ssh;
struct event ev_ssh_reconnect;
}; };
TAILQ_HEAD(tmate_ssh_clients, tmate_ssh_client); TAILQ_HEAD(tmate_ssh_clients, tmate_ssh_client);
@ -166,11 +170,17 @@ struct tmate_session {
struct tmate_ssh_clients clients; struct tmate_ssh_clients clients;
int need_passphrase; int need_passphrase;
char *passphrase; char *passphrase;
bool reconnected;
struct event ev_connection_retry;
char *last_server_ip;
char *reconnection_data;
}; };
extern struct tmate_session tmate_session; extern struct tmate_session tmate_session;
extern void tmate_session_init(struct event_base *base); extern void tmate_session_init(struct event_base *base);
extern void tmate_session_start(void); extern void tmate_session_start(void);
extern void tmate_reconnect_session(struct tmate_session *session);
/* tmate-debug.c */ /* tmate-debug.c */
extern void tmate_print_stack_trace(void); extern void tmate_print_stack_trace(void);