summaryrefslogtreecommitdiff
path: root/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'common.c')
-rw-r--r--common.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/common.c b/common.c
index 820863d..32ccc01 100644
--- a/common.c
+++ b/common.c
@@ -90,6 +90,239 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt,
syslog (prio, "%s%s", quote, buf);
}
+// --- Connector ---------------------------------------------------------------
+
+// This is a helper that tries to establish a connection with any address on
+// a given list. Sadly it also introduces a bit of a callback hell.
+
+struct connector_target
+{
+ LIST_HEADER (struct connector_target)
+
+ char *hostname; ///< Target hostname or address
+ char *service; ///< Target service name or port
+
+ struct addrinfo *results; ///< Resolved target
+ struct addrinfo *iter; ///< Current endpoint
+};
+
+static struct connector_target *
+connector_target_new (void)
+{
+ struct connector_target *self = xmalloc (sizeof *self);
+ return self;
+}
+
+static void
+connector_target_destroy (struct connector_target *self)
+{
+ free (self->hostname);
+ free (self->service);
+ freeaddrinfo (self->results);
+ free (self);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+struct connector
+{
+ int socket; ///< Socket FD for the connection
+ struct poller_fd connected_event; ///< We've connected or failed
+ struct connector_target *targets; ///< Targets
+ struct connector_target *targets_t; ///< Tail of targets
+
+ void *user_data; ///< User data for callbacks
+
+ // You may destroy the connector object in these two main callbacks:
+
+ /// Connection has been successfully established
+ void (*on_connected) (void *user_data, int socket);
+ /// Failed to establish a connection to either target
+ void (*on_failure) (void *user_data);
+
+ // Optional:
+
+ /// Connecting to a new address
+ void (*on_connecting) (void *user_data, const char *address);
+ /// Connecting to the last address has failed
+ void (*on_error) (void *user_data, const char *error);
+};
+
+static void
+connector_notify_connecting (struct connector *self,
+ struct connector_target *target, struct addrinfo *gai_iter)
+{
+ if (!self->on_connecting)
+ return;
+
+ const char *real_host = target->hostname;
+
+ // We don't really need this, so we can let it quietly fail
+ char buf[NI_MAXHOST];
+ int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
+ buf, sizeof buf, NULL, 0, NI_NUMERICHOST);
+ if (err)
+ LOG_FUNC_FAILURE ("getnameinfo", gai_strerror (err));
+ else
+ real_host = buf;
+
+ char *address = format_host_port_pair (real_host, target->service);
+ self->on_connecting (self->user_data, address);
+ free (address);
+}
+
+static void
+connector_notify_error (struct connector *self, const char *error)
+{
+ if (self->on_error)
+ self->on_error (self->user_data, error);
+}
+
+static void
+connector_prepare_next (struct connector *self)
+{
+ struct connector_target *target = self->targets;
+ if (!(target->iter = target->iter->ai_next))
+ {
+ LIST_UNLINK_WITH_TAIL (self->targets, self->targets_t, target);
+ connector_target_destroy (target);
+ }
+}
+
+static void
+connector_step (struct connector *self)
+{
+ struct connector_target *target = self->targets;
+ if (!target)
+ {
+ // Total failure, none of the targets has succeeded
+ self->on_failure (self->user_data);
+ return;
+ }
+
+ struct addrinfo *gai_iter = target->iter;
+ hard_assert (gai_iter != NULL);
+
+ connector_notify_connecting (self, target, gai_iter);
+
+ int fd = self->socket = socket (gai_iter->ai_family,
+ gai_iter->ai_socktype, gai_iter->ai_protocol);
+ if (fd == -1)
+ {
+ connector_notify_error (self, strerror (errno));
+
+ connector_prepare_next (self);
+ connector_step (self);
+ return;
+ }
+
+ set_cloexec (fd);
+ set_blocking (fd, false);
+
+ int yes = 1;
+ soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
+ &yes, sizeof yes) != -1);
+
+ if (!connect (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
+ {
+ set_blocking (fd, true);
+ self->on_connected (self->user_data, fd);
+ return;
+ }
+ else if (errno != EINPROGRESS)
+ {
+ connector_notify_error (self, strerror (errno));
+ xclose (fd);
+
+ connector_prepare_next (self);
+ connector_step (self);
+ return;
+ }
+
+ self->connected_event.fd = self->socket = fd;
+ poller_fd_set (&self->connected_event, POLLOUT);
+
+ connector_prepare_next (self);
+}
+
+static void
+connector_on_ready (const struct pollfd *pfd, struct connector *self)
+{
+ // See http://cr.yp.to/docs/connect.html if this doesn't work.
+ // The second connect() method doesn't work with DragonflyBSD.
+
+ int error = 0;
+ socklen_t error_len = sizeof error;
+ hard_assert (!getsockopt (pfd->fd,
+ SOL_SOCKET, SO_ERROR, &error, &error_len));
+
+ if (error)
+ {
+ connector_notify_error (self, strerror (errno));
+
+ xclose (self->socket);
+ self->socket = -1;
+
+ connector_step (self);
+ }
+ else
+ {
+ poller_fd_reset (&self->connected_event);
+ self->socket = -1;
+
+ set_blocking (pfd->fd, true);
+ self->on_connected (self->user_data, pfd->fd);
+ }
+}
+
+static void
+connector_init (struct connector *self, struct poller *poller)
+{
+ memset (self, 0, sizeof *self);
+ self->socket = -1;
+ poller_fd_init (&self->connected_event, poller, self->socket);
+ self->connected_event.user_data = self;
+ self->connected_event.dispatcher = (poller_fd_fn) connector_on_ready;
+}
+
+static void
+connector_free (struct connector *self)
+{
+ poller_fd_reset (&self->connected_event);
+ if (self->socket != -1)
+ xclose (self->socket);
+
+ LIST_FOR_EACH (struct connector_target, iter, self->targets)
+ connector_target_destroy (iter);
+}
+
+static bool
+connector_add_target (struct connector *self,
+ const char *hostname, const char *service, struct error **e)
+{
+ struct addrinfo hints, *results;
+ memset (&hints, 0, sizeof hints);
+ hints.ai_socktype = SOCK_STREAM;
+
+ // TODO: even this should be done asynchronously, most likely in
+ // a thread pool, similarly to how libuv does it
+ int err = getaddrinfo (hostname, service, &hints, &results);
+ if (err)
+ {
+ error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
+ return false;
+ }
+
+ struct connector_target *target = connector_target_new ();
+ target->hostname = xstrdup (hostname);
+ target->service = xstrdup (service);
+ target->results = results;
+ target->iter = target->results;
+
+ LIST_APPEND_WITH_TAIL (self->targets, self->targets_t, target);
+ return true;
+}
+
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
// These are awkward protocols. Note that the `username' is used differently