/* ========================================================================== * 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 /* snprintf(3) */ #include /* malloc(3) free(3) */ #include /* offsetof */ #include /* EINVAL ESOCKNTNOSUPPORT EINPROGRESS EINTR * EAGAIN ECONNABORTED */ #include /* strerror(3) memcpy(3) strncpy(3) */ #include /* assert(3) */ #include /* GetLastError SetLastError */ #include /* O_NONBLOCK F_SETFL F_GETFL fcntl(2) */ #include /* SLIST LIST */ #include /* MIN */ #include /* socklen_t mode_t htons(3) ntohs(3) */ #include /* struct timeval timerclear gettimeofday(2) */ #include /* umask(2) */ #if !_WIN32 #include /* 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 /* struct sockaddr_in struct sockaddr_in6 */ #include /* IPPROTO_TCP TCP_NODELAY */ #include /* struct sockaddr_un */ #include /* close(2) */ #include /* struct addrinfo struct servent getaddrinfo(3) * getservbyname(3) */ #else #include #include #endif #include /* INVALID_SOCKET closesocket() */ #include /* EV_READ EV_WRITE EV_PERSIST EV_TIMEOUT * struct event_base event_set(3) event_set_base(3) * event_add(3) */ #include #include #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() */