Skip to content

Commit

Permalink
lib-program-client: Add TCP client support
Browse files Browse the repository at this point in the history
  • Loading branch information
cmouse committed Oct 19, 2016
1 parent 204afc1 commit 366f669
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 3 deletions.
274 changes: 272 additions & 2 deletions src/lib-program-client/program-client-remote.c
Expand Up @@ -9,13 +9,16 @@
#include "eacces-error.h"
#include "istream-private.h"
#include "ostream.h"

#include "dns-lookup.h"
#include "program-client-private.h"

#include <unistd.h>
#include <sys/wait.h>
#include <sysexits.h>

static
void program_client_net_connect_again(struct program_client *pclient);

/*
* Script client input stream
*/
Expand Down Expand Up @@ -185,6 +188,17 @@ struct program_client_remote {
struct program_client client;

bool noreply:1;
bool resolved:1;

const char *hostname;
struct dns_lookup_settings dns_set;
struct dns_lookup *lookup;
unsigned int ips_count;
unsigned int ips_left;
struct ip_addr *ips;
in_port_t port;

struct timeout *to_retry;
};

static
Expand Down Expand Up @@ -262,6 +276,208 @@ int program_client_unix_connect(struct program_client *pclient)
return 0;
}

static
void program_client_net_connect_timeout(struct program_client *pclient)
{
io_remove(&pclient->io);
timeout_remove(&pclient->to);

i_error("connect(%s) failed: timeout in %u milliseconds",
pclient->path,
pclient->set.client_connect_timeout_msecs);
/* set error to timeout here */
pclient->error = PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT;
i_close_fd(&pclient->fd_out);
pclient->fd_in = pclient->fd_out = -1;
program_client_net_connect_again(pclient);
}

/* see if connect suceeded or not, if it did, then proceed
normally, otherwise try reconnect to next address */
static
void program_client_net_connected(struct program_client *pclient)
{
io_remove(&pclient->io);
if ((errno = net_geterror(pclient->fd_out)) != 0) {
i_error("connect(%s) failed: %m",
pclient->path);
/* disconnect and try again */
i_close_fd(&pclient->fd_out);
pclient->fd_in = pclient->fd_out = -1;
program_client_net_connect_again(pclient);
} else {
pclient->io = io_add(pclient->fd_out, IO_WRITE,
program_client_remote_connected, pclient);
}
}

static
void program_client_net_connect_real(struct program_client *pclient)
{
struct program_client_remote *slclient =
(struct program_client_remote *) pclient;

if (pclient->to != NULL)
timeout_remove(&pclient->to);

if (slclient->to_retry != NULL)
timeout_remove(&slclient->to_retry);

i_assert(slclient->ips_count > 0);

bool ipv6 = slclient->ips->family == AF_INET6;
pclient->path = p_strdup_printf(pclient->pool, "%s%s%s:%u",
ipv6 ? "[" : "",
net_ip2addr(slclient->ips),
ipv6 ? "]" : "",
slclient->port);

if (pclient->debug) {
i_debug("Trying to connect %s (timeout %u msecs)",
pclient->path,
pclient->set.client_connect_timeout_msecs);
}

/* try to connect */
int fd;
if ((fd = net_connect_ip(slclient->ips, slclient->port,
(slclient->ips->family == AF_INET ?
&net_ip4_any : &net_ip6_any))) < 0) {
i_error("connect(%s) failed: %m", pclient->path);
slclient->to_retry = timeout_add_short(0,
program_client_net_connect_again,
pclient);
return;
}

pclient->fd_in = (slclient->noreply && pclient->output == NULL &&
!pclient->output_seekable ? -1 : fd);
pclient->fd_out = fd;
pclient->io = io_add(fd, IO_WRITE, program_client_net_connected, pclient);

if (pclient->set.client_connect_timeout_msecs != 0) {
pclient->to = timeout_add(pclient->set.client_connect_timeout_msecs,
program_client_net_connect_timeout, pclient);
}
}

static
void program_client_net_connect_again(struct program_client *pclient)
{
struct program_client_remote *slclient =
(struct program_client_remote *) pclient;

enum program_client_error error = pclient->error;
pclient->error = PROGRAM_CLIENT_ERROR_NONE;

if (--slclient->ips_left == 0) {
if (slclient->ips_count > 1)
i_error("program-client-net: %s: No addresses left to try",
slclient->hostname);
program_client_fail(pclient,
error != PROGRAM_CLIENT_ERROR_NONE ?
error :
PROGRAM_CLIENT_ERROR_OTHER);
return;
};

slclient->ips++;
program_client_net_connect_real(pclient);
}

static
void program_client_net_connect_resolved(const struct dns_lookup_result *result,
struct program_client *pclient)
{
struct program_client_remote *slclient =
(struct program_client_remote *) pclient;

if (result->ret != 0) {
i_error("program-client-net: Cannot resolve '%s': %s",
pclient->path,
result->error);
program_client_fail(pclient, PROGRAM_CLIENT_ERROR_OTHER);
return;
}

/* reduce timeout */
if (pclient->set.client_connect_timeout_msecs > 0) {
if (pclient->set.client_connect_timeout_msecs <= result->msecs) {
/* we ran out of time */
program_client_fail(pclient,
PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT);
return;
}
pclient->set.client_connect_timeout_msecs -= result->msecs;
}

/* then connect */
slclient->ips_count = result->ips_count;
slclient->ips_left = slclient->ips_count;
slclient->ips = p_memdup(pclient->pool, result->ips,
sizeof(struct ip_addr)*result->ips_count);
program_client_net_connect_real(pclient);
}

static
int program_client_net_connect_init(struct program_client *pclient)
{
struct program_client_remote *slclient =
(struct program_client_remote *) pclient;

struct ip_addr ip;

if (slclient->ips != NULL) {
slclient->hostname = p_strdup(pclient->pool,
net_ip2addr(slclient->ips));
} else if (net_addr2ip(pclient->path, &ip) == 0) {
slclient->hostname = p_strdup(pclient->pool,
net_ip2addr(&ip));
slclient->resolved = TRUE;
slclient->ips = p_new(pclient->pool, struct ip_addr, 1);
*slclient->ips = ip;
slclient->ips_count = 1;
} else {
slclient->resolved = FALSE;
slclient->hostname = p_strdup(pclient->pool, pclient->path);
if (pclient->set.dns_client_socket_path != NULL) {
slclient->dns_set.dns_client_socket_path =
pclient->set.dns_client_socket_path;
slclient->dns_set.timeout_msecs =
pclient->set.client_connect_timeout_msecs;
dns_lookup(pclient->path, &slclient->dns_set,
program_client_net_connect_resolved,
pclient, &slclient->lookup);
return 0;
} else {
struct ip_addr *ips;
unsigned int ips_count;
int err;
/* guess we do it here then.. */
if ((err = net_gethostbyname(pclient->path,
&ips, &ips_count)) != 0) {
i_error("program-client-remote: "
"Cannot resolve '%s': %s",
pclient->path,
net_gethosterror(err));
return -1;
}
slclient->ips_count = ips_count;
slclient->ips = p_memdup(pclient->pool,
ips,
sizeof(*ips)*ips_count);
}
}

slclient->ips_left = slclient->ips_count;
slclient->to_retry = timeout_add_short(0,
program_client_net_connect_real,
pclient);

return 0;
}


static
int program_client_remote_close_output(struct program_client *pclient)
{
Expand Down Expand Up @@ -317,8 +533,14 @@ void program_client_remote_disconnect(struct program_client *pclient, bool force
}

static
void program_client_remote_switch_ioloop(struct program_client *pclient ATTR_UNUSED)
void program_client_remote_switch_ioloop(struct program_client *pclient)
{
struct program_client_remote *slclient =
(struct program_client_remote *)pclient;
if (slclient->to_retry != NULL)
slclient->to_retry = io_loop_move_timeout(&slclient->to_retry);
if (slclient->lookup)
dns_lookup_switch_ioloop(slclient->lookup);
}

struct program_client *
Expand All @@ -340,3 +562,51 @@ program_client_unix_create(const char *socket_path, const char *const *args,

return &pclient->client;
}

struct program_client *
program_client_net_create(const char *host, in_port_t port,
const char *const *args,
const struct program_client_settings *set,
bool noreply)
{
struct program_client_remote *pclient;
pool_t pool;

pool = pool_alloconly_create("program client net", 1024);
pclient = p_new(pool, struct program_client_remote, 1);
program_client_init(&pclient->client, pool, host, args, set);
pclient->port = port;
pclient->client.connect = program_client_net_connect_init;
pclient->client.close_output = program_client_remote_close_output;
pclient->client.disconnect = program_client_remote_disconnect;
pclient->noreply = noreply;

return &pclient->client;
}

struct program_client *
program_client_net_create_ips(const struct ip_addr *ips, size_t ips_count,
in_port_t port,
const char *const *args,
const struct program_client_settings *set,
bool noreply)
{
struct program_client_remote *pclient;
pool_t pool;

i_assert(ips != NULL && ips_count > 0);

pool = pool_alloconly_create("program client net", 1024);
pclient = p_new(pool, struct program_client_remote, 1);
program_client_init(&pclient->client, pool, net_ip2addr(ips), args, set);
pclient->port = port;
pclient->client.connect = program_client_net_connect_init;
pclient->client.close_output = program_client_remote_close_output;
pclient->client.disconnect = program_client_remote_disconnect;
pclient->client.switch_ioloop = program_client_remote_switch_ioloop;
pclient->noreply = noreply;
pclient->ips = p_memdup(pool, ips,
sizeof(struct ip_addr)*ips_count);
pclient->ips_count = ips_count;
return &pclient->client;
}
11 changes: 10 additions & 1 deletion src/lib-program-client/program-client.h
Expand Up @@ -5,6 +5,7 @@
#define PROGRAM_CLIENT_H

#include "restrict-access.h"
#include "net.h"

struct program_client;

Expand All @@ -15,6 +16,7 @@ struct program_client_settings {
restrict_access_init(&set.restrict_set);
*/
struct restrict_access_settings restrict_set;
const char *dns_client_socket_path;
const char *home;

bool allow_root:1;
Expand All @@ -31,7 +33,14 @@ struct program_client *program_client_local_create(const char *bin_path,
struct program_client *program_client_unix_create(const char *socket_path,
const char *const *args,
const struct program_client_settings *set, bool noreply);

struct program_client *program_client_net_create(const char *host, in_port_t port,
const char *const *args,
const struct program_client_settings *set, bool noreply);
struct program_client *
program_client_net_create_ips(const struct ip_addr *ips, size_t ips_count,
in_port_t port, const char *const *args,
const struct program_client_settings *set,
bool noreply);
void program_client_destroy(struct program_client **_pclient);

void program_client_set_input(struct program_client *pclient,
Expand Down

0 comments on commit 366f669

Please sign in to comment.