/* ==========================================================================
* libevnet/src/socket.c - Network server library for libevent.
* --------------------------------------------------------------------------
* Copyright (c) 2006 William Ahern
* Copyright (c) 2006 Barracuda Networks, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#include <stdio.h> /* snprintf(3) */
#include <stdlib.h> /* malloc(3) free(3) */
#include <stddef.h> /* offsetof */
#include <errno.h> /* EINVAL ESOCKNTNOSUPPORT EINPROGRESS EINTR
* EAGAIN ECONNABORTED */
#include <string.h> /* strerror(3) memcpy(3) strncpy(3) */
#include <assert.h> /* assert(3) */
#include <windows.h> /* GetLastError SetLastError */
#include <fcntl.h> /* O_NONBLOCK F_SETFL F_GETFL fcntl(2) */
#include <sys/queue.h> /* SLIST LIST */
#include <sys/param.h> /* MIN */
#include <sys/types.h> /* socklen_t mode_t htons(3) ntohs(3) */
#include <sys/time.h> /* struct timeval timerclear gettimeofday(2) */
#include <sys/stat.h> /* umask(2) */
#if !_WIN32
#include <sys/socket.h> /* SOMAXCONN AF_UNIX AF_INET AF_INET6 SOCK_STREAM
* PF_UNIX PF_INET PF_INET6 SOL_SOCKET SO_ERROR
* struct sockaddr socket(2) connect(2) getsockopt(2) */
#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */
#include <netinet/tcp.h>/* IPPROTO_TCP TCP_NODELAY */
#include <sys/un.h> /* struct sockaddr_un */
#include <unistd.h> /* close(2) */
#include <netdb.h> /* struct addrinfo struct servent getaddrinfo(3)
* getservbyname(3) */
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <winsock2.h> /* INVALID_SOCKET closesocket() */
#include <event.h> /* EV_READ EV_WRITE EV_PERSIST EV_TIMEOUT
* struct event_base event_set(3) event_set_base(3)
* event_add(3) */
#include <arena/proto.h>
#include <arena/util.h>
#include "socket.h"
#include "lookup.h"
#include "tls.h"
#include "bufio.h"
#include "bufio/socket.h"
#ifndef MARK
#define MARK (fprintf(stderr, "@%s:%d\n", __FUNCTION__, __LINE__))
#endif
struct socket;
struct socket_accept;
struct socket_connect;
const char *socket_errlist[] = {
[SOCKET_ESUCCESS] = "Success",
[SOCKET_ESYSTEM] = "System error",
[SOCKET_ETIMEDOUT] = "Operation timed out",
[SOCKET_ECANCELLED] = "Operation cancelled",
[SOCKET_ENOTFOUND] = "Hostname not found",
[SOCKET_ENOTLS] = "Unable to negotiate TLS/SSL",
[SOCKET_ENOTCONNECTED] = "Socket not connected",
}; /* socket_errlist[] */
const int socket_nerr = sizeof socket_errlist / sizeof *socket_errlist;
const struct socket_options socket_defaults = {
.sun_mask = 0000,
.sun_unlink = 0,
.sa_reuseaddr = 0,
.so_type = SOCK_STREAM,
.so_nonblock = 1,
.so_backlog = SOMAXCONN,
.sin_resolv = 0,
.sin_resolv = 0,
}; /* socket_defaults */
#define SOCKET_FRAME_PUSH(p, f) do { \
(f)->xp = &(p); \
SLIST_INSERT_HEAD(&(p)->frames, (f), sle); \
} while(0)
#define SOCKET_FRAME_OKAY(f) (*(f)->xp != 0)
#define SOCKET_FRAME_POP(p, f) do { \
if (SOCKET_FRAME_OKAY((f))) { \
assert((f) == SLIST_FIRST(&(p)->frames)); \
SLIST_REMOVE_HEAD(&(p)->frames, sle); \
} \
} while(0)
#define SOCKET_FRAME_KILL(f) (*(f)->xp = 0)
struct socket_frame {
struct socket **xp;
SLIST_ENTRY(socket_frame) sle;
}; /* struct socket_frame */
struct socket_accept_frame {
struct socket_accept **xp;
SLIST_ENTRY(socket_accept_frame) sle;
}; /* struct socket_accept_frame */
struct socket_connect_frame {
struct socket_connect **xp;
SLIST_ENTRY(socket_connect_frame) sle;
}; /* struct socket_connect_frame */
enum socket_state {
SOCKET_STATE_QUIET,
SOCKET_STATE_LOOKUP,
SOCKET_STATE_POLLING,
SOCKET_STATE_RLOOKUP,
SOCKET_STATE_START_TLS,
SOCKET_STATE_CONNECTED,
}; /* enum socket_state */
struct socket_endpoint {
struct sockaddr_storage ss;
char *hostname;
}; /* struct socket_endpoint */
struct socket_accept {
struct socket *socket;
SOCKET fd;
enum socket_state state;
struct socket_endpoint local, peer;
struct {
void (*fn)(struct socket *, struct socket *, enum socket_errno, void *);
void *arg;
} cb;
struct {
struct event event;
int pending;
} ev;
struct tls *tls;
struct timeval expire;
LIST_ENTRY(socket_accept) le;
SLIST_HEAD(, socket_accept_frame) frames;
}; /* struct socket_accept */
struct socket_connect {
struct socket *socket;
SOCKET fd;
enum socket_state state;
struct {
struct sockaddr_storage *buf;
struct sockaddr_storage *pos;
struct sockaddr_storage *end;
} ss;
struct socket_endpoint local, peer;
struct {
void (*fn)(struct socket *, enum socket_errno, void *);
void *arg;
} cb;
struct {
struct event event;
int pending;
} ev;
struct tls *tls;
struct timeval expire;
LIST_ENTRY(socket_connect) le;
SLIST_HEAD(, socket_connect_frame) frames;
}; /* struct socket_connect */
struct socket {
SOCKET fd;
enum socket_mode mode;
int state;
enum socket_errno so_errno;
struct socket_options opts;
struct event_base *base;
const struct arena_prototype *ap;
struct lookup *lu;
SLIST_HEAD(, socket_frame) frames;
LIST_HEAD(, socket_accept) accept;
LIST_HEAD(, socket_connect) connect;
struct {
struct tls_identity *identity;
struct tls *session;
void (*cb)(struct socket *, enum socket_errno, void *);
void *arg;
} tls;
struct socket_name name;
struct socket_endpoint local, peer;
struct bufio_socket iobuf;
LIST_ENTRY(socket) le;
}; /* struct socket */
static int so_set_nonblock(SOCKET fd) {
#ifdef O_NONBLOCK
int flags;
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
return -1;
if (0 != fcntl(fd, F_SETFL, flags | O_NONBLOCK))
return -1;
#elif defined FIONBIO
unsigned long nonblock = 1;
if (0 != ioctlsocket(fd, FIONBIO, &nonblock))
return -1;
#else
#error Do not know how to enable non-blocking I/O
#endif
return 0;
} /* so_set_nonblock() */
static SOCKET so_open(struct sockaddr_storage *ss, int so_type) {
SOCKET sd = INVALID_SOCKET;
switch (ss->ss_family) {
case AF_INET:
sd = socket(PF_INET, so_type, 0);
break;
#ifdef USE_IPV6
case AF_INET6:
sd = socket(PF_INET6, so_type, 0);
break;
#endif
#ifndef WIN32
case AF_UNIX:
sd = socket(PF_UNIX, so_type, 0);
break;
#endif
default:
#if _WIN32
SetLastError(EINVAL);
#else
SetLastError(ESOCKTNOSUPPORT);
#endif
} /* switch (ss_family) */
return sd;
} /* so_open() */
static struct socket_connect *socket_connect_open(struct socket *s, enum socket_errno *so_errno) {
static const struct socket_connect s_initializer;
struct socket_connect *c = 0;
int sys_errno;
if (!(c = s->ap->malloc(s->ap, sizeof *c, 0))) {
*so_errno = SOCKET_ESYSTEM;
return 0;
}
*c = s_initializer;
c->fd = -1;
c->socket = s;
return c;
} /* socket_connect_open() */
static void socket_connect_close(struct socket *s, struct socket_connect *c, int notify) {
socket_connect_cb cb;
void *arg;
assert(c->state != SOCKET_STATE_LOOKUP && c->state != SOCKET_STATE_RLOOKUP);
if (c->ev.pending)
(void)event_del(&c->ev.event), c->ev.pending = 0;
if (c->fd != INVALID_SOCKET)
(void)closesocket(c->fd), c->fd = INVALID_SOCKET;
tls_close(c->tls), c->tls = 0;
s->ap->free(s->ap, c->ss.buf), c->ss.buf = 0;
cb = c->cb.fn;
arg = c->cb.arg;
s->ap->free(s->ap, c);
if (notify && cb != 0)
cb(s, s->so_errno = SOCKET_ECANCELLED, arg);
return /* void */;
} /* socket_connect_close() */
static struct socket_accept *socket_accept_open(struct socket *s, enum socket_errno *so_errno) {
static const struct socket_accept a_initializer;
struct socket_accept *a = 0;
int sys_errno;
if (!(a = s->ap->malloc(s->ap, sizeof *a, 0))) {
*so_errno = SOCKET_ESYSTEM;
return 0;
}
*a = a_initializer;
a->fd = -1;
a->socket = s;
return a;
} /* socket_accept_open() */
static void socket_accept_close(struct socket *s, struct socket_accept *a, int notify) {
socket_accept_cb cb = a->cb.fn;
void *arg = a->cb.arg;
assert(a->state != SOCKET_STATE_LOOKUP);
if (a->ev.pending)
(void)event_del(&a->ev.event), a->ev.pending = 0;
if (a->fd != INVALID_SOCKET)
(void)closesocket(a->fd), a->fd = INVALID_SOCKET;
tls_close(a->tls), a->tls = 0;
s->ap->free(s->ap, a->peer.hostname);
s->ap->free(s->ap, a->local.hostname);
s->ap->free(s->ap, a);
if (notify && cb != 0)
cb(s, 0, s->so_errno = SOCKET_ECANCELLED, arg);
return /* void */;
} /* socket_accept_close() */
struct socket *socket_open(struct socket_name *name, const struct socket_options *opts, struct event_base *base, const struct arena_prototype *ap, enum socket_errno *so_errno) {
static const struct socket s_initializer;
struct socket *s = 0;
enum bufio_errno io_errno;
int sys_errno;
assert(so_errno != 0);
assert(name != 0);
if (!opts)
opts = &socket_defaults;
if (!ap)
ap = ARENA_STDLIB;
if (!(s = ap->malloc(ap, sizeof *s, 0)))
goto sysfail;
*s = s_initializer;
s->fd = -1;
s->opts = *opts;
s->base = base;
s->ap = ap;
s->name = *name;
if (0 == bufio_socket_init(&s->iobuf, BUFIO_SOCKET_FD_WAIT, &bufio_socket_defaults, base, ap, &io_errno))
goto sysfail;
if (name->type == &name->addr)
s->name.type = &s->name.addr;
else if (name->type == &name->host)
s->name.type = &s->name.host;
#if !_WIN32
else if (name->type == &name->local)
s->name.type = &s->name.local;
#endif
else
goto sysfail;
SLIST_INIT(&s->frames);
return s;
sysfail:
*so_errno = SOCKET_ESYSTEM;
anyfail:
sys_errno = GetLastError();
ap->free(ap, s);
SetLastError(sys_errno);
return 0;
} /* socket_open() */
static struct socket *socket_clone(struct socket *s, enum socket_errno *so_errno) {
struct socket *s1;
if (0 == (s1 = socket_open(&s->name, &s->opts, s->base, s->ap, so_errno)))
return 0;
s1->mode = s->mode;
s1->tls.identity = s->tls.identity;
return s1;
} /* socket_clone() */
void socket_cancel(struct socket *s, int notify) {
struct socket_frame f;
struct socket_accept *a;
struct socket_connect *c;
SOCKET_FRAME_PUSH(s, &f);
while (SOCKET_FRAME_OKAY(&f) && LIST_END(&s->accept) != (a = LIST_FIRST(&s->accept))) {
struct socket_accept_frame *fp;
LIST_REMOVE(a, le);
SLIST_FOREACH(fp, &a->frames, sle)
SOCKET_FRAME_KILL(fp);
SLIST_INIT(&a->frames);
socket_accept_close(s, a, notify);
}
while (SOCKET_FRAME_OKAY(&f) && LIST_END(&s->connect) != (c = LIST_FIRST(&s->connect))) {
struct socket_connect_frame *fp;
LIST_REMOVE(c, le);
SLIST_FOREACH(fp, &c->frames, sle)
SOCKET_FRAME_KILL(fp);
SLIST_INIT(&c->frames);
socket_connect_close(s, c, notify);
}
SOCKET_FRAME_POP(s, &f);
return /* void */;
} /* socket_cancel() */
void socket_close(struct socket *s) {
struct socket_frame f, *fp;
if (!s)
return /* void */;
SOCKET_FRAME_PUSH(s, &f);
socket_cancel(s, 0);
if (!SOCKET_FRAME_OKAY(&f))
return /* void */;
SOCKET_FRAME_POP(s, &f);
/*
* Kill all previous frames. When we return all their object
* references will become invalid.
*/
SLIST_FOREACH(fp, &s->frames, sle)
SOCKET_FRAME_KILL(fp);
SLIST_INIT(&s->frames);
tls_close(s->tls.session), s->tls.session = 0;
lookup_close(s->lu), s->lu = 0;
if (bufio_socket_initialized(&s->iobuf))
bufio_socket_destroy(&s->iobuf);
if (s->fd != INVALID_SOCKET)
(void)closesocket(s->fd), s->fd = INVALID_SOCKET;
s->ap->free(s->ap, s->peer.hostname);
s->ap->free(s->ap, s->local.hostname);
s->ap->free(s->ap, s);
return /* void */;
} /* socket_close() */
void socket_enable_tls(struct socket *s, struct tls_identity *tlsid) {
assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));
s->tls.identity = tlsid;
return /* void */;
} /* socket_enable_tls() */
void socket_getsockinfo(struct socket *s, SOCKET *fd, struct tls **tls) {
if (fd != 0)
*fd = s->fd;
if (tls != 0)
*tls = s->tls.session;
return /* void */;
} /* socket_getsockinfo() */
void socket_discard(struct socket *s, SOCKET *fd, struct tls **tls) {
if (fd != 0) {
*fd = s->fd;
s->fd = -1;
}
if (tls != 0) {
*tls = s->tls.session;
s->tls.session = 0;
}
socket_close(s);
return /* void */;
} /* socket_discard() */
enum socket_errno socket_getpeername(struct socket *s, struct sockaddr *sa, socklen_t *salen, const char **host) {
if (s->state != SOCKET_STATE_CONNECTED && s->state != SOCKET_STATE_START_TLS)
return SOCKET_ENOTCONNECTED;
if (sa != 0) {
assert(salen != 0);
*salen = MIN(*salen, SOCKET_SA_LENOF(&s->peer.ss));
(void)memcpy(sa, &s->peer.ss, *salen);
}
if (host != 0)
*host = s->peer.hostname;
return 0;
} /* socket_getpeername() */
enum socket_errno socket_getsockname(struct socket *s, struct sockaddr *sa, socklen_t *salen, const char **host) {
if (s->state != SOCKET_STATE_CONNECTED && s->state != SOCKET_STATE_START_TLS)
return SOCKET_ENOTCONNECTED;
if (sa != 0) {
assert(salen != 0);
*salen = MIN(*salen, SOCKET_SA_LENOF(&s->local.ss));
(void)memcpy(sa, &s->local.ss, *salen);
}
if (host != 0)
*host = s->local.hostname;
return 0;
} /* socket_getsockname() */
enum socket_errno socket_setsockstate(struct socket *s, SOCKET fd, enum socket_mode mode) {
enum bufio_errno io_errno;
socklen_t salen;
s->fd = fd;
if (0 == bufio_socket_init(&s->iobuf, s->fd, 0, s->base, s->ap, &io_errno))
goto sysfail;
s->mode = mode;
s->state = SOCKET_STATE_CONNECTED;
(void)getsockname(fd, (struct sockaddr *)&s->local.ss, (salen = sizeof s->local.ss, &salen));
(void)getpeername(fd, (struct sockaddr *)&s->peer.ss, (salen = sizeof s->peer.ss, &salen));
return 0;
sysfail:
s->fd = -1;
return SOCKET_ESYSTEM;
} /* socket_setsockstate() */
struct bufio_source *socket_to_source(struct socket *s) {
assert(bufio_socket_initialized(&s->iobuf));
return bufio_socket_to_source(&s->iobuf);
} /* socket_to_source() */
struct bufio_sink *socket_to_sink(struct socket *s) {
assert(bufio_socket_initialized(&s->iobuf));
return bufio_socket_to_sink(&s->iobuf);
} /* socket_to_sink() */
static void socket_accept_throw(struct socket *s, struct socket_accept *a, enum socket_errno so_errno) {
struct socket *s1 = 0;
socket_accept_cb cb = a->cb.fn;
void *arg = a->cb.arg;
enum bufio_errno bio_errno;
int sys_errno;
if (so_errno != 0)
goto anyfail;
if (0 == (s1 = socket_clone(s, &so_errno)))
goto anyfail;
if (0 == bufio_socket_init(&s1->iobuf, a->fd, 0, s1->base, s1->ap, &bio_errno))
goto sysfail;
if (a->tls)
bufio_socket_switch_tls(&s1->iobuf, a->tls);
s1->state = SOCKET_STATE_CONNECTED;
s1->tls.session = a->tls;
a->tls = 0;
s1->peer = a->peer;
a->peer.hostname = 0;
s1->local = a->local;
a->local.hostname = 0;
s1->fd = a->fd;
a->fd = -1;
LIST_REMOVE(a, le);
socket_accept_close(s, a, 0);
cb(s, s1, s->so_errno = 0, arg);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
sys_errno = GetLastError();
socket_close(s1);
LIST_REMOVE(a, le);
socket_accept_close(s, a, 0);
SetLastError(sys_errno);
cb(s, 0, s->so_errno = so_errno, arg);
return /* void */;
} /* socket_accept_throw() */
static void socket_accept_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
struct socket_accept *a = arg;
struct socket *s = a->socket;
a->state = 0;
socket_accept_throw(s, a, (tls_errno == 0)? 0 : SOCKET_ENOTLS);
return /* void */;
} /* socket_accept_catch_tls() */
static void socket_accept_start_tls(struct socket *s, struct socket_accept *a) {
enum tls_errno tls_errno;
a->state = 0;
if (!s->tls.identity) {
socket_accept_throw(s, a, 0);
return /* void */;
}
/* FIXME: Bubble up TLS and OpenSSL errors. */
if (!(a->tls = tls_open(a->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
socket_accept_throw(s, a, SOCKET_ENOTLS);
return /* void */;
}
/* FIXME: Add timeout capability. */
tls_accept(a->tls, &socket_accept_catch_tls, a, 0);
return /* void */;
} /* socket_accept_start_tls() */
static void socket_accept_rresolved(int numans, struct lookup_rr *ans, int numadd, struct lookup_rr *add, enum lookup_errno lu_errnp, void *arg) {
struct socket_accept *a = arg;
struct socket *s = a->socket;
a->state = 0;
if (numans > 0 && ans->type == LOOKUP_IN_PTR) {
if ((a->peer.hostname = s->ap->malloc(s->ap, ans->rr.ptr.hostlen + 1, 1)))
(void)snprintf(a->peer.hostname, ans->rr.ptr.hostlen + 1, "%.*s", (int)ans->rr.ptr.hostlen, ans->rr.ptr.host);
}
socket_accept_start_tls(s, a);
return /* void */;
} /* socket_accept_rresolved() */
static void socket_accept_rlookup(struct socket *s, struct socket_accept *a) {
#ifdef USE_CARES
if (!s->opts.sin_resolv) {
socket_accept_start_tls(s, a);
return /* void */;
}
if (s->lu == 0 && !(s->lu = lookup_open(0, s->base, s->ap))) {
socket_accept_throw(s, a, SOCKET_ESYSTEM);
return /* void */;
}
a->state = SOCKET_STATE_RLOOKUP;
/* FIXME: Suport timeout. */
lookup_ptr(s->lu, (struct sockaddr *)&a->peer.ss, SOCKET_SA_LENOF(&a->peer.ss), 0, &socket_accept_rresolved, a, 0);
return /* void */;
#else
socket_accept_start_tls(s, a);
return /* void */;
#endif /* USE_CARES */
} /* socket_accept_rlookup() */
static void socket_accept_wake(int, short, void *);
static enum socket_errno socket_accept_poll(struct socket *s, struct socket_accept *a) {
struct timeval now, timeout, expire;
/* Short circuit if we're already pending without a timeout. */
if (a->ev.pending && !timerisset(&a->expire))
return 0;
if (timerisset(&a->expire)) {
assert(0 == gettimeofday(&now, 0));
if (timercmp(&now, &a->expire, <)) {
timersub(&a->expire, &now, &timeout);
} else { /* Hmmm, we snuck past the deadline.... */
timerclear(&timeout);
timeout.tv_usec = 1;
}
} else
timerclear(&timeout);
if (a->ev.pending)
(void)event_del(&a->ev.event), a->ev.pending = 0;
event_set(&a->ev.event, a->fd, EV_READ | EV_PERSIST, socket_accept_wake, a);
if (s->base)
event_base_set(s->base, &a->ev.event);
if (0 != event_add(&a->ev.event, timerisset(&timeout)? &timeout : 0))
return SOCKET_ESYSTEM;
a->ev.pending = 1;
return 0;
} /* socket_accept_poll() */
static void socket_accept_wake(SOCKET fd, short events, void *arg) {
struct { struct socket_frame s; struct socket_accept_frame a; } f;
struct socket_accept *a = arg;
struct socket *s = a->socket;
socket_accept_cb cb = a->cb.fn;
struct socket_accept *a1;
enum socket_errno so_errno;
int sys_errno;
struct sockaddr_storage ss;
socklen_t salen;
int i;
/* Stick to the a->fd reference so we don't get confused later. */
fd = INVALID_SOCKET;
arg = a->cb.arg;
SOCKET_FRAME_PUSH(s, &f.s);
SOCKET_FRAME_PUSH(a, &f.a);
if (events & EV_TIMEOUT) {
so_errno = SOCKET_ETIMEDOUT;
goto anyfail;
}
for (i = 0; i < 3 && SOCKET_FRAME_OKAY(&f.a); i++) {
salen = sizeof ss;
if (INVALID_SOCKET == (fd = accept(a->fd, (struct sockaddr *)&ss, &salen))) {
switch (GetLastError()) {
case EINTR:
/* FALL THROUGH */
case EAGAIN:
/* FALL THROUGH */
#ifdef ECONNABORTED
case ECONNABORTED:
/* FALL THROUGH */
#endif
#ifdef ECONNRESET
case ECONNRESET:
/* FALL THROUGH */
#endif
#ifdef WSAEWOULDBLOCK
case WSAEWOULDBLOCK:
/* FALL THROUGH */
#endif
if (0 != (so_errno = socket_accept_poll(s, a)))
goto anyfail;
goto endcall;
default:
goto sysfail;
}
}
if (s->opts.so_nodelay
&& 0 != setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&s->opts.so_nodelay, sizeof s->opts.so_nodelay))
goto sysfail;
if (s->opts.so_nonblock && 0 != so_set_nonblock(fd))
goto sysfail;
/* If there was an expiration, this was a oneshot deal. */
if (timerisset(&a->expire))
socket_cancel(s, 0);
assert(SOCKET_FRAME_OKAY(&f.s));
/*
* Fork this accept so the new one can finish up connection
* establishment and callback
*/
if (0 == (a1 = socket_accept_open(s, &so_errno)))
goto anyfail;
a1->fd = fd;
fd = -1;
a1->cb = a->cb;
a1->local = a->local;
a1->peer.ss = ss;
if (a->local.hostname != 0
&& 0 == (a1->local.hostname = ap_strdup(a1->socket->ap, a->local.hostname)))
goto anyfail;
if (0 != getsockname(a1->fd, (struct sockaddr *)&a1->local.ss, (salen = sizeof a1->local.ss, &salen)))
goto anyfail;
LIST_INSERT_HEAD(&s->accept, a1, le);
/* Out of our hands, now. */
socket_accept_rlookup(s, a1);
} /* while (i++ < 3) */
endcall:
SOCKET_FRAME_POP(a, &f.a);
SOCKET_FRAME_POP(s, &f.s);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
assert(SOCKET_FRAME_OKAY(&f.s));
sys_errno = GetLastError();
if (fd != INVALID_SOCKET)
(void)closesocket(fd);
socket_cancel(s, 0);
SetLastError(sys_errno);
cb(s, 0, s->so_errno = so_errno, arg);
SOCKET_FRAME_POP(a, &f.a);
SOCKET_FRAME_POP(s, &f.s);
return /* void */;
} /* socket_accept_wake() */
static enum socket_errno socket_accept_bind(struct socket *s, struct socket_accept *a, struct sockaddr_storage *ss) {
mode_t omask;
int sys_errno;
(void)memcpy(&a->local.ss, ss, sizeof a->local.ss);
if (INVALID_SOCKET == (a->fd = so_open(ss, s->opts.so_type)))
return SOCKET_ESYSTEM;
if (s->opts.sa_reuseaddr
&& 0 != setsockopt(a->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&s->opts.sa_reuseaddr, sizeof s->opts.sa_reuseaddr))
return SOCKET_ESYSTEM;
if (0 != so_set_nonblock(a->fd))
return SOCKET_ESYSTEM;
#if !_WIN32
if (ss->ss_family == AF_UNIX && s->opts.sun_unlink) {
char path[sizeof (((struct sockaddr_un *)ss)->sun_path) + 1];
path[sizeof path - 1] = '\0';
/* Should we fail if errno != ENOENT? */
(void)unlink(strncpy(path, ((struct sockaddr_un *)ss)->sun_path, sizeof path - 1));
}
#endif
omask = umask(s->opts.sun_mask);
if (0 != bind(a->fd, (struct sockaddr *)ss, SOCKET_SA_LENOF(ss))) {
sys_errno = GetLastError();
(void)umask(omask);
SetLastError(sys_errno);
return SOCKET_ESYSTEM;
}
(void)umask(omask);
if (0 != listen(a->fd, s->opts.so_backlog))
return SOCKET_ESYSTEM;
return 0;
} /* socket_accept_bind() */
/*
* Safe. Never enters a callback and so never alters our stack state.
* Caller is responsible for error handling.
*/
static enum socket_errno socket_accept_all(struct socket *s, unsigned num, struct sockaddr_storage *ss) {
struct socket_accept *a0, *a;
struct servent *ent;
long port;
char *end;
enum socket_errno so_errno;
unsigned i;
assert(0 != (a0 = LIST_FIRST(&s->accept)));
assert(num > 0 && ss != 0);
if (s->name.type == &s->name.host) {
port = strtol(s->name.host.port, &end, 10);
if (port < 0 || port > 65535 || *end != '\0') {
if (!(ent = getservbyname(s->name.host.port, (s->opts.so_type == SOCK_DGRAM)? "udp" : "tcp")))
return SOCKET_ESYSTEM;
port = ntohs(ent->s_port);
}
for (i = 0; i < num; i++) {
switch (ss[i].ss_family) {
case AF_INET:
((struct sockaddr_in *)&ss[i])->sin_port = htons(port);
break;
#if USE_IPV6
case AF_INET6:
((struct sockaddr_in6 *)&ss[i])->sin6_port = htons(port);
break;
#endif
default:
return SOCKET_ESYSTEM;
}
}
} /* if (s->name.type == &s->name.host) */
for (i = 0;;) {
assert(a = LIST_FIRST(&s->accept));
a->cb = a0->cb;
if (0 != (so_errno = socket_accept_bind(s, a, &ss[i])))
return so_errno;
if (0 != (so_errno = socket_accept_poll(s, a)))
return so_errno;
a->state == SOCKET_STATE_POLLING;
if (++i >= num)
break;
if (0 == (a = socket_accept_open(s, &so_errno)))
return so_errno;
LIST_INSERT_HEAD(&s->accept, a, le);
}
return 0;
} /* socket_accept_all() */
static void socket_accept_resolved(int numans, struct lookup_rr *ans, int numadd, struct lookup_rr *add, enum lookup_errno lu_errno, void *arg) {
struct socket *s = arg;
struct sockaddr_storage *ss = 0;
struct socket_accept *a;
socket_accept_cb cb;
void *p;
unsigned i;
struct lookup_rr *rr;
int so_errno, sys_errno;
assert((a = LIST_FIRST(&s->accept)));
a->state = 0;
cb = a->cb.fn;
arg = a->cb.arg;
if (lu_errno == LOOKUP_ETIMEDOUT) {
so_errno = SOCKET_ETIMEDOUT;
goto anyfail;
} else if (LOOKUP_FAILURE(lu_errno)) {
so_errno = SOCKET_ENOTFOUND;
goto anyfail;
}
for (i = 0, rr = ans; rr != 0; rr = rr->next) {
if (rr->type != LOOKUP_IN_A && rr->type != LOOKUP_IN_AAAA)
continue;
if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
goto sysfail;
ss = p;
(void)memcpy(&ss[i++], &rr->rr.ip.sa, rr->rr.ip.salen);
}
if (i == 0) {
so_errno = SOCKET_ENOTFOUND;
goto anyfail;
}
if (0 != (so_errno = socket_accept_all(s, i, ss)))
goto anyfail;
s->ap->free(s->ap, ss);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
sys_errno = GetLastError();
socket_cancel(s, 0);
s->ap->free(s->ap, ss);
SetLastError(sys_errno);
cb(s, 0, s->so_errno = so_errno, arg);
return /* void */;
} /* socket_accept_resolved() */
void socket_accept(struct socket *s, socket_accept_cb cb, void *arg, struct timeval *timeout) {
struct socket_accept *a = 0;
struct sockaddr_storage *ss = 0;
#if USE_IPV6
struct addrinfo hints, *res, *res0 = 0;
#else
struct hostent *ent;
#endif
struct timeval now;
void *p;
unsigned i;
enum socket_errno so_errno;
int sys_errno;
assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));
s->mode = SOCKET_MODE_SERVER;
if (!(a = socket_accept_open(s, &so_errno)))
goto anyfail;
LIST_INSERT_HEAD(&s->accept, a, le);
a->cb.fn = cb;
a->cb.arg = arg;
if (timeout != 0 && timerisset(timeout)) {
assert(0 == gettimeofday(&now, 0));
timeradd(&now, timeout, &a->expire);
} else
timerclear(&a->expire);
if (s->name.type == &s->name.host) {
#if USE_IPV6
(void)memset(&hints, 0, sizeof hints);
#ifdef USE_CARES
hints.ai_flags = AI_NUMERICHOST;
#endif
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = s->opts.so_type;
if (0 == getaddrinfo(s->name.host.name, s->name.host.port, &hints, &res0)) {
assert(res0 != 0);
for (i = 0, res = res0; res != 0; i++, res = res->ai_next) {
if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
goto sysfail;
else
ss = p;
memcpy(&ss[i], res->ai_addr, res->ai_addrlen);
}
if (0 != (so_errno = socket_accept_all(s, i, ss)))
goto anyfail;
freeaddrinfo(res0), res0 = 0;
s->ap->free(s->ap, ss), ss = 0;
return /* void */;
#else
if (0 != (ent = gethostbyname(s->name.host.name))) {
struct sockaddr_in *sin;
unsigned short port;
if (!(ss = s->ap->malloc(s->ap, sizeof *ss, 0)))
goto sysfail;
sin = memset(ss, '\0', sizeof *ss);
sin->sin_family = AF_INET;
/* FIXME: Don't use atoi! */
port = atoi(s->name.host.port);
sin->sin_port = htons(port);
assert(sizeof sin->sin_addr == ent->h_length);
(void)memcpy(&sin->sin_addr, ent->h_addr, ent->h_length);
if (0 != (so_errno = socket_accept_all(s, 1, ss)))
goto anyfail;
s->ap->free(s->ap, ss), ss = 0;
return /* void */;
#endif
} else {
#ifdef USE_CARES
if (!(s->lu = lookup_open(0, s->base, s->ap))) {
/* FIXME: Propogate lookup errors. */
so_errno = SOCKET_ESYSTEM;
goto anyfail;
}
a->state = SOCKET_STATE_LOOKUP;
lookup_rr(s->lu, s->name.host.name, strlen(s->name.host.name), s->name.host.rtype, s->name.host.flags, &socket_accept_resolved, s, timeout);
return /* void */;
#else
so_errno = SOCKET_ESYSTEM;
goto anyfail;
#endif /* USE_CARES */
}
#if !_WIN32
} else if (s->name.type == &s->name.local) {
struct sockaddr_storage ss;
struct sockaddr_un *sun;
sun = memset(&ss, 0, sizeof ss);
sun->sun_family = AF_UNIX;
strncpy(sun->sun_path, s->name.local.path, sizeof sun->sun_path);
if (0 != (so_errno = socket_accept_all(s, 1, &ss)))
goto anyfail;
return /* void */;
#endif
} else if (0 != (so_errno = socket_accept_all(s, 1, &s->name.addr.ss)))
goto anyfail;
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
sys_errno = GetLastError();
socket_cancel(s, 0);
#if USE_IPV6
if (res0 != 0)
freeaddrinfo(res0);
#endif
s->ap->free(s->ap, ss);
SetLastError(sys_errno);
cb(s, 0, s->so_errno = so_errno, arg);
return /* void */;
} /* socket_accept() */
static void socket_connect_throw(struct socket *s, struct socket_connect *c, enum socket_errno so_errno) {
socket_connect_cb cb = c->cb.fn;
void *arg = c->cb.arg;
enum bufio_errno bio_errno;
int sys_errno;
if (so_errno != 0)
goto anyfail;
s->state = SOCKET_STATE_CONNECTED;
s->tls.session = c->tls;
c->tls = 0;
s->peer = c->peer;
c->peer.hostname = 0;
s->local = c->local;
c->local.hostname = 0;
s->fd = c->fd;
c->fd = -1;
LIST_REMOVE(c, le);
socket_connect_close(s, c, 0);
if (s->tls.session)
bufio_socket_switch_tls(&s->iobuf, s->tls.session);
if (bufio_socket_initialized(&s->iobuf)) {
bufio_socket_set_fd(&s->iobuf, s->fd);
} else if (0 == bufio_socket_init(&s->iobuf, s->fd, 0, s->base, s->ap, &bio_errno))
goto sysfail;
cb(s, s->so_errno = 0, arg);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
sys_errno = GetLastError();
LIST_REMOVE(c, le);
socket_connect_close(s, c, 0);
/* Fail anything polling by sliding in an invalid desciptor. */
if (bufio_socket_initialized(&s->iobuf))
bufio_socket_set_fd(&s->iobuf, -1);
SetLastError(sys_errno);
cb(s, s->so_errno = so_errno, arg);
return /* void */;
} /* socket_connect_throw() */
static void socket_connect_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
struct socket *s = arg;
struct socket_connect *c;
assert((c = LIST_FIRST(&s->connect)));
c->state = 0;
/* FIXME: Try to failover to next address if TLS fails? */
socket_connect_throw(s, c, (tls_errno == 0)? 0 : SOCKET_ENOTLS);
return /* void */;
} /* socket_connect_catch_tls() */
static void socket_connect_start_tls(struct socket *s, struct socket_connect *c) {
enum tls_errno tls_errno;
c->state = 0;
if (!s->tls.identity) {
socket_connect_throw(s, c, 0);
return /* void */;
}
/* FIXME: Bubble up TLS and OpenSSL errors. */
if (!(c->tls = tls_open(c->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
socket_connect_throw(s, c, SOCKET_ENOTLS);
return /* void */;
}
c->state = SOCKET_STATE_START_TLS;
/* FIXME: Add timeout capability. */
tls_connect(c->tls, &socket_connect_catch_tls, s, 0);
return /* void */;
} /* socket_connect_start_tls() */
static void socket_connect_setup(struct socket *, struct socket_connect *);
static void socket_connect_wake(int fd, short event, void *arg) {
struct socket *s = arg;
struct socket_connect *c;
enum socket_errno so_errno;
struct timeval now, timeout;
struct sockaddr *sa;
socklen_t salen;
int sd, sys_errno;
assert((c = LIST_FIRST(&s->connect)));
c->state = 0;
if (event & EV_TIMEOUT)
goto timeout;
if (c->ev.pending) {
int so_errno;
socklen_t n = sizeof so_errno;
c->ev.pending = 0; /* Setup as one-shot below. */
if (0 != getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *)&so_errno, &n))
goto sysfail;
if (0 != so_errno) {
errno = so_errno;
goto sysfail;
}
} else {
if (0 != connect(c->fd, SOCKET_SA_P(c->ss.pos), SOCKET_SA_LENOF(c->ss.pos))) {
switch (GetLastError()) {
#ifdef EINPROGRESS
case EINPROGRESS:
#endif
#ifdef WSAEINPROGRESS
case WSAEINPROGRESS:
#endif
#ifdef WSAEWOULDBLOCK
case WSAEWOULDBLOCK:
#endif
goto poll;
default:
goto sysfail;
}
}
}
sa = (struct sockaddr *)&c->peer.ss;
salen = sizeof c->peer.ss;
if (0 != getpeername(c->fd, sa, &salen))
goto sysfail;
sa = (struct sockaddr *)&c->local.ss;
salen = sizeof c->local.ss;
if (0 != getsockname(c->fd, sa, &salen))
goto sysfail;
socket_connect_start_tls(s, c);
return /* void */;
poll:
if (0 != gettimeofday(&now, 0))
goto sysfail;
if (timerisset(&c->expire)) {
if (timercmp(&now, &c->expire, >=))
goto timeout;
timersub(&c->expire, &now, &timeout);
} else
timerclear(&timeout);
event_set(&c->ev.event, c->fd, EV_WRITE, &socket_connect_wake, s);
if (s->base)
event_base_set(s->base, &c->ev.event);
if (0 != event_add(&c->ev.event, timerisset(&timeout)? &timeout : 0))
goto sysfail;
c->ev.pending = 1;
c->state = SOCKET_STATE_POLLING;
return /* void */;
timeout:
socket_connect_throw(s, c, SOCKET_ETIMEDOUT);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
if (++c->ss.pos < c->ss.end) {
if (c->fd != INVALID_SOCKET)
(void)closesocket(c->fd), c->fd = INVALID_SOCKET;
socket_connect_setup(s, c);
return /* void */;
}
socket_connect_throw(s, c, so_errno);
return /* void */;
} /* socket_connect_wake() */
static void socket_connect_setup(struct socket *s, struct socket_connect *c) {
int sys_errno;
nextsa:
assert(c->ss.pos < c->ss.end);
while (c->ss.pos < c->ss.end) {
if (INVALID_SOCKET != (c->fd = so_open(c->ss.pos, s->opts.so_type)))
break;
else if (c->ss.pos >= c->ss.end - 1)
break;
else
c->ss.pos++;
}
if (c->fd == INVALID_SOCKET)
goto sysfail;
if (s->opts.so_nodelay
&& 0 != setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (void *)&s->opts.so_nodelay, sizeof s->opts.so_nodelay))
goto sysfail;
if (0 != so_set_nonblock(c->fd))
goto sysfail;
socket_connect_wake(c->fd, EV_WRITE, s);
return /* void */;
sysfail:
sys_errno = GetLastError();
if (c->fd != INVALID_SOCKET)
(void)closesocket(c->fd), c->fd = INVALID_SOCKET;
if (c->ss.pos < c->ss.end - 1)
goto nextsa;
SetLastError(sys_errno);
socket_connect_throw(s, c, SOCKET_ESYSTEM);
return /* void */;
} /* socket_connect_setup() */
static void socket_connect_resolved(int nans, struct lookup_rr *ans, int nadd, struct lookup_rr *add, enum lookup_errno lu_errno, void *arg) {
struct socket *s = arg;
struct socket_connect *c;
int sys_errno;
struct lookup_rr *r;
struct servent *ent;
long port;
char *end;
assert(c = LIST_FIRST(&s->connect));
c->state = 0;
if (LOOKUP_FAILURE(lu_errno)) {
goto dnsfail;
} else if (!(nans > 0)) {
sys_errno = EFAULT;
goto sysfail;
} else if (!(c->ss.buf = s->ap->malloc(s->ap, nans * sizeof *c->ss.buf, 0)))
goto sysfail;
port = strtol(s->name.host.port, &end, 10);
if (port < 0 || port > 65535 || *end != '\0') {
if (!(ent = getservbyname(s->name.host.port, (s->opts.so_type == SOCK_DGRAM)? "udp" : "tcp")))
goto sysfail;
port = ntohs(ent->s_port);
}
for (r = ans, c->ss.pos = c->ss.buf; r != 0; r = r->next) {
switch (r->type) {
case LOOKUP_IN_A:
r->rr.ip.sa.sin.sin_port = htons(port);
break;
#if USE_IPV6
case LOOKUP_IN_AAAA:
r->rr.ip.sa.sin6.sin6_port = htons(port);
break;
#endif
default:
continue;
}
(void)memcpy(c->ss.pos, &r->rr.ip.sa, r->rr.ip.salen);
c->ss.pos++;
}
c->ss.end = c->ss.pos;
c->ss.pos = c->ss.buf;
if (c->ss.end - c->ss.pos == 0) {
sys_errno = EFAULT;
goto sysfail;
}
socket_connect_setup(s, c);
return /* void */;
sysfail:
lu_errno = LOOKUP_ESYSTEM;
/* FALL THROUGH */
dnsfail:
switch (lu_errno) {
case LOOKUP_ESYSTEM:
socket_connect_throw(s, c, SOCKET_ESYSTEM);
break;
case LOOKUP_ETIMEDOUT:
socket_connect_throw(s, c, SOCKET_ETIMEDOUT);
break;
default:
socket_connect_throw(s, c, SOCKET_ENOTFOUND);
break;
}
return /* void */;
} /* socket_connect_resolved() */
void socket_connect(struct socket *s, socket_connect_cb cb, void *arg, struct timeval *timeout) {
struct socket_connect *c = 0;
struct sockaddr_storage *ss = 0;
#if USE_IPV6
struct addrinfo hints, *res, *res0 = 0;
#else
struct hostent *ent;
#endif
struct timeval now;
void *p;
unsigned i;
enum socket_errno so_errno;
int sys_errno;
assert(LIST_EMPTY(&s->connect) && LIST_EMPTY(&s->accept));
s->mode = SOCKET_MODE_CLIENT;
if (!(c = socket_connect_open(s, &so_errno)))
goto anyfail;
LIST_INSERT_HEAD(&s->connect, c, le);
c->cb.fn = cb;
c->cb.arg = arg;
if (timeout != 0 && timerisset(timeout)) {
assert(0 == gettimeofday(&now, 0));
timeradd(&now, timeout, &c->expire);
} else
timerclear(&c->expire);
if (s->name.type == &s->name.host) {
#if USE_IPV6
(void)memset(&hints, 0, sizeof hints);
#ifdef USE_CARES
hints.ai_flags = AI_NUMERICHOST;
#endif
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = s->opts.so_type;
if (0 == getaddrinfo(s->name.host.name, s->name.host.port, &hints, &res0)) {
assert(res0 != 0);
for (i = 0, res = res0; res != 0; i++, res = res->ai_next) {
if (!(p = s->ap->realloc(s->ap, ss, sizeof *ss * (i + 1), 0)))
goto sysfail;
else
ss = p;
memcpy(&ss[i], res->ai_addr, res->ai_addrlen);
}
c->ss.buf = ss;
c->ss.pos = ss;
c->ss.end = ss + i;
ss = 0;
freeaddrinfo(res0), res0 = 0;
/* FALL THROUGH */
#else
if (0 != (ent = gethostbyname(s->name.host.name))) {
struct sockaddr_in *sin;
unsigned short port;
if (!(c->ss.buf = s->ap->malloc(s->ap, sizeof *ss, 0)))
goto sysfail;
c->ss.pos = c->ss.buf;
c->ss.end = c->ss.pos + 1;
sin = memset(c->ss.buf, '\0', sizeof *ss);
sin->sin_family = AF_INET;
/* FIXME: Don't use atoi! */
port = atoi(s->name.host.port);
sin->sin_port = htons(port);
assert(sizeof sin->sin_addr == ent->h_length);
(void)memcpy(&sin->sin_addr, ent->h_addr, ent->h_length);
#endif
} else {
#ifdef USE_CARES
if (!(s->lu = lookup_open(0, s->base, s->ap))) {
/* FIXME: Propogate lookup errors. */
so_errno = SOCKET_ESYSTEM;
goto anyfail;
}
c->state = SOCKET_STATE_LOOKUP;
lookup_rr(s->lu, s->name.host.name, strlen(s->name.host.name), s->name.host.rtype, s->name.host.flags, &socket_connect_resolved, s, timeout);
return /* void */;
#else
so_errno = SOCKET_ESYSTEM;
goto anyfail;
#endif /* USE_CARES */
}
#if !_WIN32
} else if (s->name.type == &s->name.local) {
struct sockaddr_un *sun;
if (0 == (c->ss.buf = s->ap->malloc(s->ap, sizeof *c->ss.buf, 0)))
goto sysfail;
c->ss.pos = c->ss.buf;
c->ss.end = c->ss.buf + 1;
sun = memset(c->ss.buf, 0, sizeof *c->ss.buf);
sun->sun_family = AF_UNIX;
strncpy(sun->sun_path, s->name.local.path, sizeof sun->sun_path);
/* FALL THROUGH */
#endif
} else {
if (0 == (c->ss.buf = s->ap->malloc(s->ap, sizeof *c->ss.buf, 0)))
goto sysfail;
c->ss.pos = memcpy(c->ss.buf, &s->name.addr.ss, sizeof *c->ss.buf);
c->ss.end = c->ss.buf + 1;
/* FALL THROUGH */
}
socket_connect_setup(s, c);
return /* void */;
sysfail:
so_errno = SOCKET_ESYSTEM;
/* FALL THROUGH */
anyfail:
sys_errno = GetLastError();
socket_cancel(s, 0);
#if USE_IPV6
if (res0 != 0)
freeaddrinfo(res0);
#endif
s->ap->free(s->ap, ss);
SetLastError(sys_errno);
cb(s, s->so_errno = so_errno, arg);
return /* void */;
} /* socket_connect() */
void socket_catch_tls(struct tls *tls, enum tls_errno tls_errno, void *arg) {
struct socket *s = arg;
void (*cb)(struct socket *, enum socket_errno, void *);
s->state = SOCKET_STATE_CONNECTED;
cb = s->tls.cb;
s->tls.cb = 0;
arg = s->tls.arg;
s->tls.arg = 0;
switch (tls_errno) {
case TLS_ESUCCESS:
bufio_socket_switch_tls(&s->iobuf, s->tls.session);
cb(s, s->so_errno = SOCKET_ESUCCESS, arg);
break;
case TLS_ETIMEDOUT:
cb(s, s->so_errno = SOCKET_ETIMEDOUT, arg);
break;
default:
cb(s, s->so_errno = SOCKET_ENOTLS, arg);
break;
}
return /* void */;
} /* socket_catch_tls() */
void socket_start_tls(struct socket *s, struct tls_identity *tlsid, void (*cb)(struct socket *, enum socket_errno, void *), void *arg, struct timeval *timeout) {
enum tls_errno tls_errno;
assert(!s->tls.session);
s->tls.identity = tlsid;
if (!(s->tls.session = tls_open(s->fd, s->tls.identity, s->base, s->ap, &tls_errno))) {
cb(s, s->so_errno = SOCKET_ENOTLS, arg);
return /* void */;
}
s->state = SOCKET_STATE_START_TLS;
s->tls.cb = cb;
s->tls.arg = arg;
if (s->mode == SOCKET_MODE_SERVER)
tls_accept(s->tls.session, &socket_catch_tls, s, timeout);
else
tls_connect(s->tls.session, &socket_catch_tls, s, timeout);
return /* void */;
} /* socket_start_tls() */
struct socket_name *socket_name_init_addr(struct socket_name *name, const struct sockaddr *sa, socklen_t salen) {
name->type = &name->addr;
(void)memcpy(&name->addr.sa, sa, salen);
return name;
} /* socket_name_init_addr() */
struct socket_name *socket_name_init_host(struct socket_name *name, const char *host, const char *port, int rtype, int flags) {
name->type = &name->host;
(void)snprintf(name->host.name, sizeof name->host.name, "%s", host);
(void)snprintf(name->host.port, sizeof name->host.port, "%s", port);
name->host.rtype = rtype;
name->host.flags = flags;
return name;
} /* socket_name_init_host() */
#if !_WIN32
struct socket_name *socket_name_init_unix(struct socket_name *name, const char *path) {
name->type = &name->local;
(void)snprintf(name->local.path, sizeof name->local.path, "%s", path);
return name;
} /* socket_name_init_unix() */
#endif
enum socket_errno socket_errno(struct socket *s) {
return s->so_errno;
} /* socket_errno() */
const char *socket_strerror(enum socket_errno so_errno) {
return socket_errlist[so_errno];
} /* socket_strerror() */
const char *socket_errstring(struct socket *s) {
return socket_errlist[s->so_errno];
} /* socket_errstring() */
void socket_getoption_so_type(struct socket *s, int *t) {
*t = s->opts.so_type;
return /* void */;
} /* socket_getoption_so_type() */
void socket_getoption_so_nonblock(struct socket *s, int *b) {
*b = s->opts.so_nonblock;
return /* void */;
} /* socket_getoption_so_nonblock() */
void socket_getoption_so_mode(struct socket *s, enum socket_mode *m) {
*m = s->mode;
return /* void */;
} /* socket_getoption_so_mode() */
syntax highlighted by Code2HTML, v. 0.9.1