Support for multiple IP on DNS

This commit is contained in:
Nicolas Viennot 2013-07-22 17:06:28 -04:00
parent 2e3661a0f6
commit 36bfa71b78
9 changed files with 254 additions and 68 deletions

View File

@ -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 \

View File

@ -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])

View File

@ -187,7 +187,7 @@ server_start(int lockfd, char *lockfile)
}
}
tmate_client_start();
tmate_session_start();
cmdq_continue(cfg_cmd_q);

View File

@ -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);
}

View File

@ -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)

107
tmate-session.c Normal file
View File

@ -0,0 +1,107 @@
#include <event2/dns.h>
#include <event2/util.h>
#include <event2/event.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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();
}

View File

@ -1,9 +1,8 @@
#include <libssh/libssh.h>
#include <libssh/callbacks.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <event.h>
#include <assert.h>
#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);
}

18
tmate.c
View File

@ -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();
}

57
tmate.h
View File

@ -4,6 +4,7 @@
#include <sys/types.h>
#include <msgpack.h>
#include <libssh/libssh.h>
#include <libssh/callbacks.h>
#include <event.h>
#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 */