mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
856 lines
20 KiB
856 lines
20 KiB
/**
|
|
* Copyright (C) 2007 iptego GmbH
|
|
*
|
|
* This file is part of libbinrpc.
|
|
*
|
|
* libbinrpc is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* libbinrpc is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <stdlib.h>
|
|
#include <netdb.h>
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/select.h>
|
|
#include <limits.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/tcp.h>
|
|
#include <stdio.h>
|
|
|
|
#include "tls.h"
|
|
#include "mem.h"
|
|
#include "errno.h"
|
|
#include "log.h"
|
|
#include "config.h"
|
|
#include "misc.h"
|
|
#include "net.h"
|
|
|
|
#define BRPC_SCHEME_PREF "brpc"
|
|
#define BRPC_SCHEME_POSTF "://"
|
|
|
|
__LOCAL bool resolve_inet(char *name, brpc_addr_t *addr)
|
|
{
|
|
struct addrinfo hint, *result;
|
|
int res;
|
|
|
|
memset((char *)&hint, 0, sizeof(struct addrinfo));
|
|
hint.ai_flags = AI_PASSIVE|AI_ALL;
|
|
hint.ai_family = addr->domain;
|
|
hint.ai_socktype = addr->socktype;
|
|
|
|
if ((res = getaddrinfo(name, NULL, &hint, &result)) != 0) {
|
|
WERRNO(ERESLV);
|
|
ERR("failed to resolve name `%s': %s.\n", name, gai_strerror(res));
|
|
return false;
|
|
}
|
|
assert(result->ai_socktype == addr->socktype);
|
|
memcpy((char *)&addr->sockaddr, result->ai_addr, result->ai_addrlen);
|
|
addr->domain = result->ai_family;
|
|
addr->addrlen = result->ai_addrlen;
|
|
|
|
freeaddrinfo(result);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* brpcns://asta.la.vista.baby
|
|
* brpcnd://1.2.3.4:5555
|
|
* brpcns://[1.2.3.4]:5555
|
|
* brpcls:///var/run/binrpc.usock
|
|
* brpcld://~bpi/test.ldom
|
|
*/
|
|
brpc_addr_t *brpc_parse_uri(const char *_uri)
|
|
{
|
|
static __brpc_tls brpc_addr_t addr;
|
|
long port;
|
|
char *uri;
|
|
char *pos, *resume;
|
|
size_t addrlen;
|
|
char pres[INET6_ADDRSTRLEN];
|
|
void *addrp;
|
|
|
|
DBG("parsing `%s' [%zd] @%p\n", _uri, strlen(_uri), _uri);
|
|
/* check against min-len. sane URI */
|
|
if ((! _uri) || (strlen(_uri) < sizeof(BRPC_SCHEME_PREF) - 1
|
|
+ /* addr type discr. */1 + /* sock type discr. */1
|
|
+ sizeof(BRPC_SCHEME_POSTF) - 1
|
|
+ /* at least one char as address */1)) {
|
|
WERRNO(EINVAL);
|
|
ERR("invalid URI `%s'.\n", _uri);
|
|
return NULL;
|
|
}
|
|
|
|
uri = strdup(_uri);
|
|
if (! uri) {
|
|
WERRNO(ENOMEM);
|
|
return NULL;
|
|
}
|
|
|
|
memset((char *)&addr, 0, sizeof(brpc_addr_t));
|
|
|
|
pos = uri;
|
|
if (strncasecmp(pos, BRPC_SCHEME_PREF,
|
|
sizeof(BRPC_SCHEME_PREF) - 1) != 0) {
|
|
WERRNO(EINVAL);
|
|
ERR("invalid scheme in URI `%s'.\n", _uri);
|
|
goto error;
|
|
}
|
|
pos += sizeof(BRPC_SCHEME_PREF) - 1;
|
|
|
|
/* get address family */
|
|
switch (*pos | 0x20) {
|
|
case 'n': addr.domain = AF_UNSPEC; break;
|
|
case '4': addr.domain = PF_INET; break;
|
|
case '6': addr.domain = PF_INET6; break;
|
|
case 'l': addr.domain = PF_LOCAL; break;
|
|
default:
|
|
WERRNO(EINVAL);
|
|
ERR("invalid address type selector '%c' in URI `%s'.\n",
|
|
*pos, _uri);
|
|
goto error;
|
|
}
|
|
pos ++;
|
|
|
|
/* get socket type */
|
|
switch (*pos | 0x20) {
|
|
case 's': addr.socktype = SOCK_STREAM; break;
|
|
case 'd': addr.socktype = SOCK_DGRAM; break;
|
|
default:
|
|
WERRNO(EINVAL);
|
|
ERR("invalid socket type selector '%c' in URI `%s'.\n",
|
|
*pos, _uri);
|
|
goto error;
|
|
}
|
|
pos ++;
|
|
pos += sizeof(BRPC_SCHEME_POSTF) - 1;
|
|
|
|
if (addr.domain == PF_LOCAL) {
|
|
addr.sockaddr.un.sun_family = AF_LOCAL;
|
|
addrlen = strlen(pos) + /*0-term*/1;
|
|
if (sizeof(addr.sockaddr.un.sun_path) < addrlen) {
|
|
WERRNO(E2BIG);
|
|
ERR("local domain address `%s' too long (maximum: %zd) in URI "
|
|
"`%s'.\n", pos, sizeof(addr.sockaddr.un.sun_path), _uri);
|
|
goto error;
|
|
}
|
|
memcpy(addr.sockaddr.un.sun_path, pos, addrlen);
|
|
addr.addrlen = SUN_LEN(&addr.sockaddr.un);
|
|
INFO("path in URI `%s' resolved to `%s'.\n", _uri, pos);
|
|
} else {
|
|
if (*pos == '[') {
|
|
pos ++;
|
|
if (! (resume = strchr(pos, ']'))) {
|
|
WERRNO(EINVAL);
|
|
ERR("invalid address in URI `%s' (missing ']').\n", _uri);
|
|
goto error;
|
|
} else {
|
|
*resume = 0;
|
|
resume ++; /* now pointing either to : or \0 */
|
|
if (*resume) {
|
|
if (*resume != ':') {
|
|
WERRNO(EINVAL);
|
|
ERR("invalid URI `%s' (unexpected char at `%s').\n",
|
|
_uri, resume);
|
|
}
|
|
*resume = 0;
|
|
resume ++;
|
|
}
|
|
}
|
|
} else if ((resume = strchr(pos, ':'))) {
|
|
if (strchr(resume + 1, ':')) /*IPv6 hex*/
|
|
resume = NULL;
|
|
else {
|
|
*resume = 0; /* delimit hostname */
|
|
resume ++;
|
|
}
|
|
}
|
|
|
|
if (! resolve_inet(pos, &addr)) {
|
|
WERRNO(EADDRNOTAVAIL);
|
|
ERR("failed to resolve address `%s' in URI `%s'.\n", pos, _uri);
|
|
goto error;
|
|
}
|
|
|
|
pos = resume;
|
|
if (pos) {
|
|
errno = 0;
|
|
if (((port = strtol(pos, NULL, 10)) == 0) && errno) {
|
|
WERRNO(EINVAL);
|
|
ERR("failed to read port `%s' as integer in URI `%s'.\n",
|
|
pos, _uri);
|
|
goto error;
|
|
}
|
|
DBG("string `%s' decoded as integer %ld.\n", pos, port);
|
|
if ((1 << (8 * sizeof(short))) <= port) {
|
|
WERRNO(EINVAL);
|
|
ERR("invalid port number %u (to high) in URI `%s'.\n",
|
|
port, _uri);
|
|
goto error;
|
|
}
|
|
} else {
|
|
DBG("using default port %u.\n", BINRPC_PORT);
|
|
port = BINRPC_PORT;
|
|
}
|
|
|
|
if (addr.domain == PF_INET) {
|
|
addr.sockaddr.in4.sin_port = htons(port);
|
|
addrp = (void *)&addr.sockaddr.in4.sin_addr;
|
|
} else {
|
|
addr.sockaddr.in6.sin6_port = htons(port);
|
|
addrp = (void *)&addr.sockaddr.in6.sin6_addr;
|
|
}
|
|
|
|
if (! inet_ntop(addr.domain, addrp, pres, sizeof(pres)))
|
|
WARN("failed to get presentation for address in URI `%s'.\n",
|
|
_uri);
|
|
else
|
|
INFO("address in URI `%s' resolved to `%s'; port: %ld.\n",
|
|
_uri, pres, port);
|
|
}
|
|
|
|
free(uri);
|
|
return &addr;
|
|
error:
|
|
free(uri);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
char *brpc_print_addr(const brpc_addr_t *addr)
|
|
{
|
|
/* assume sun_path's larger than INET6_ADDRSTRLEN */
|
|
#define BUFF_SIZE /* brpcXY:// */9 + /*[]*/2 + \
|
|
sizeof(((struct sockaddr_un *)0)->sun_path) + /*:*/1 + \
|
|
/*port presentation*/5 + /*0-term*/1
|
|
#define PROTO_POS 4
|
|
#define STYPE_POS 5
|
|
#define ADDR_OFFT 9
|
|
static __brpc_tls char buff[BUFF_SIZE] = BRPC_URI_PREFIX;
|
|
char *pos;
|
|
|
|
switch (addr->domain) {
|
|
case PF_LOCAL:
|
|
buff[PROTO_POS] = 'l';
|
|
memcpy(buff + ADDR_OFFT, addr->sockaddr.un.sun_path,
|
|
strlen(addr->sockaddr.un.sun_path) + /*0-term*/1);
|
|
break;
|
|
case PF_INET:
|
|
buff[PROTO_POS] = '4';
|
|
pos = buff + ADDR_OFFT;
|
|
if (! inet_ntop(addr->domain, &addr->sockaddr.in4.sin_addr, pos,
|
|
sizeof(buff) - ADDR_OFFT)) {
|
|
WSYSERRNO;
|
|
return NULL;
|
|
}
|
|
while (*pos)
|
|
pos ++;
|
|
snprintf(pos, /*:*/1 + /*short*/5 + /*0-term*/1, ":%d",
|
|
ntohs(addr->sockaddr.in4.sin_port));
|
|
break;
|
|
case PF_INET6:
|
|
buff[PROTO_POS] = '6';
|
|
pos = buff + ADDR_OFFT;
|
|
*pos = '['; pos ++;
|
|
if (! inet_ntop(addr->domain, &addr->sockaddr.in6.sin6_addr, pos,
|
|
sizeof(buff) - ADDR_OFFT)) {
|
|
WSYSERRNO;
|
|
return NULL;
|
|
}
|
|
while (*pos)
|
|
pos ++;
|
|
snprintf(pos, /*]*/1 + /*:*/1 +/*short*/5 + /*0-term*/1, "]:%d",
|
|
ntohs(addr->sockaddr.in6.sin6_port));
|
|
break;
|
|
|
|
default:
|
|
BUG("unsupported value as protocol specifier: %d.\n",
|
|
addr->domain);
|
|
WERRNO(EINVAL);
|
|
return NULL;
|
|
}
|
|
switch (addr->socktype) {
|
|
case SOCK_STREAM: buff[STYPE_POS] = 's'; break;
|
|
case SOCK_DGRAM: buff[STYPE_POS] = 'd'; break;
|
|
default:
|
|
BUG("unsupported value as socket type specifier: %d.\n",
|
|
addr->socktype);
|
|
WERRNO(EINVAL);
|
|
return NULL;
|
|
}
|
|
|
|
return buff;
|
|
|
|
#undef BUFF_SIZE
|
|
#undef PROTO_POS
|
|
#undef STYPE_POS
|
|
#undef ADDR_OFFT
|
|
}
|
|
|
|
bool brpc_addr_eq(const brpc_addr_t *a, const brpc_addr_t *b)
|
|
{
|
|
if (a->domain != b->domain)
|
|
return false;
|
|
if (a->socktype != b->socktype)
|
|
return false;
|
|
if (memcmp((char *)&a->sockaddr, (char *)&b->sockaddr,
|
|
sizeof(a->sockaddr)))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
__LOCAL bool setsockmode(int sockfd, bool blocking, bool *_prev)
|
|
{
|
|
int optval;
|
|
bool prev;
|
|
|
|
if ((optval = fcntl(sockfd, F_GETFL, 0)) < 0) {
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
prev = ! (optval & O_NONBLOCK);
|
|
if (blocking == prev)
|
|
/* nothing to do: mode already as desired */
|
|
goto done;
|
|
if (! blocking)
|
|
optval |= O_NONBLOCK;
|
|
else
|
|
optval &= ~O_NONBLOCK;
|
|
if (fcntl(sockfd, F_SETFL, optval) == -1) {
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
done:
|
|
if (_prev)
|
|
*_prev = prev;
|
|
return true;
|
|
}
|
|
|
|
__LOCAL bool setsocktos(int sockfd)
|
|
{
|
|
int optval;
|
|
/* TODO: does it matter _IP/_IP6? */
|
|
optval = IPTOS_LOWDELAY|IPTOS_THROUGHPUT|IPTOS_RELIABILITY;
|
|
if (setsockopt(sockfd, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) == -1){
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
__LOCAL bool disable_nagle(int sockfd)
|
|
{
|
|
int optval = 1;
|
|
if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY,
|
|
&optval, sizeof(optval)) == -1) {
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int brpc_socket(brpc_addr_t *addr, bool blocking, bool named)
|
|
{
|
|
int sockfd;
|
|
int optval;
|
|
|
|
sockfd = socket(addr->domain, addr->socktype, /*proto: kern choice */0);
|
|
if (sockfd < 0) {
|
|
WSYSERRNO;
|
|
return sockfd;
|
|
}
|
|
|
|
if (! setsockmode(sockfd, blocking, NULL))
|
|
goto error;
|
|
|
|
|
|
optval = 1;
|
|
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
|
sizeof(optval)) != 0) {
|
|
WSYSERRNO;
|
|
goto error;
|
|
}
|
|
|
|
switch (addr->domain) {
|
|
case AF_INET:
|
|
case AF_INET6:
|
|
if (! setsocktos(sockfd))
|
|
WARN("failed to set TOS.\n");
|
|
}
|
|
|
|
if (named) {
|
|
/* remove any previous socket file */
|
|
if (addr->domain == PF_LOCAL) {
|
|
if(unlink(addr->sockaddr.un.sun_path) < 0) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
case ENOTDIR: /* allow failure at right place (bind) */
|
|
break;
|
|
default:
|
|
WSYSERRNO;
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bind(sockfd, (struct sockaddr *)&addr->sockaddr,
|
|
addr->addrlen) != 0) {
|
|
WSYSERRNO;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
return sockfd;
|
|
error:
|
|
close(sockfd);
|
|
return -1;
|
|
}
|
|
|
|
bool brpc_connect(brpc_addr_t *addr, int *_sockfd, brpc_tv_t tout)
|
|
{
|
|
int sockfd;
|
|
fd_set rset, wset;
|
|
struct timeval tv;
|
|
int res, err;
|
|
socklen_t elen;
|
|
bool was_blocking;
|
|
|
|
if ((*_sockfd < 0) && ((*_sockfd = brpc_socket(addr, false, false)) < 0))
|
|
return false;
|
|
sockfd = *_sockfd;
|
|
|
|
/* set nonblocking so that it can timeout */
|
|
if (! setsockmode(sockfd, false, &was_blocking))
|
|
goto error;
|
|
switch (addr->domain) {
|
|
case AF_INET:
|
|
case AF_INET6:
|
|
if (! setsocktos(sockfd))
|
|
WARN("failed to set TOS.\n");
|
|
}
|
|
#ifndef DISABLE_NAGLE
|
|
/* NAGLE's good in this case: save one packet (ACK), locally, and read
|
|
* more in one syscall, remotely */
|
|
if ((addr->domain == PF_INET) && (addr->socktype == SOCK_STREAM))
|
|
if (disable_nagle(sockfd))
|
|
WARN("failed to disable Nagle for socket [%d:%d] (%s).\n",
|
|
addr->domain, addr->socktype, brpc_strerror());
|
|
#endif
|
|
|
|
errno = 0;
|
|
if (connect(sockfd, (struct sockaddr *)&addr->sockaddr,
|
|
addr->addrlen) == 0)
|
|
return sockfd;
|
|
else if (errno != EINPROGRESS) {
|
|
WSYSERRNO;
|
|
goto error;
|
|
}
|
|
|
|
FD_ZERO(&rset);
|
|
FD_SET(sockfd, &rset);
|
|
wset = rset;
|
|
|
|
if (tout) {
|
|
tv.tv_sec = tout / 1000000LU;
|
|
tv.tv_usec = tout - (tv.tv_sec * 1000000LU);
|
|
}
|
|
|
|
if ((res = select(sockfd + 1, &rset, &wset, NULL,
|
|
tout ? &tv : NULL)) < 0) {
|
|
WSYSERRNO;
|
|
goto error;
|
|
} else if (res == 0) {
|
|
WERRNO(ETIMEDOUT);
|
|
goto error;
|
|
}
|
|
|
|
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
|
|
elen = sizeof(err);
|
|
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &elen) < 0)
|
|
goto error;
|
|
|
|
if (err) {
|
|
WERRNO(err);
|
|
goto error;
|
|
}
|
|
} else {
|
|
BUG("select returned %d for one descriptor, timeout not signaled,"
|
|
" but no file descriptor set; socket: %d.\n", res, sockfd);
|
|
abort();
|
|
}
|
|
|
|
/* reinstate previous blocking mode */
|
|
if (! setsockmode(sockfd, was_blocking, NULL))
|
|
goto error;
|
|
|
|
return true;
|
|
error:
|
|
close(sockfd);
|
|
*_sockfd = -1;
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Error codes:
|
|
* EINPROGRESS : timeout occured while something had been sent.
|
|
* ETIMEDOUT : timeout occured but nothing sent.
|
|
* EMSGSIZE : message to large, but nothing sent.
|
|
* rest are 'socket-fatal'.
|
|
*/
|
|
bool brpc_sendto(int sockfd, brpc_addr_t *dest, brpc_t *msg, brpc_tv_t tout)
|
|
{
|
|
uint8_t *pos;
|
|
ssize_t offt;
|
|
size_t still;
|
|
const brpc_bin_t *buff;
|
|
fd_set wset;
|
|
brpc_tv_t prev, now;
|
|
struct timeval tv;
|
|
ssize_t sent;
|
|
struct sockaddr *saddr;
|
|
socklen_t saddrlen;
|
|
|
|
if (dest) {
|
|
DBG("sending to %s.\n", brpc_print_addr(dest));
|
|
saddr = (struct sockaddr *)&dest->sockaddr;
|
|
saddrlen = dest->addrlen;
|
|
} else {
|
|
saddr = NULL;
|
|
saddrlen = 0;
|
|
}
|
|
|
|
if (! (buff = brpc_serialize(msg)))
|
|
return false;
|
|
|
|
pos = buff->val;
|
|
still = buff->len;
|
|
offt = 0;
|
|
|
|
#ifndef FIX_FALSE_GCC_WARNS
|
|
if (tout)
|
|
#endif
|
|
prev = brpc_now();
|
|
while (still) {
|
|
FD_ZERO(&wset);
|
|
FD_SET(sockfd, &wset);
|
|
|
|
if (tout) {
|
|
now = brpc_now();
|
|
tout -= now - prev;
|
|
prev = now;
|
|
tv.tv_sec = tout / 1000000LU;
|
|
tv.tv_usec = tout - (tv.tv_sec * 1000000LU);
|
|
}
|
|
DBG("timer armed to %lu.\n", tout);
|
|
|
|
switch (select(sockfd + 1, NULL, &wset, NULL, tout ? &tv : NULL)) {
|
|
case 0:
|
|
if (offt)
|
|
WERRNO(EINPROGRESS);
|
|
else
|
|
WERRNO(ETIMEDOUT);
|
|
return false;
|
|
case 1:
|
|
break;
|
|
default:
|
|
switch (errno) {
|
|
case EINTR: /* signal delivered */
|
|
continue;
|
|
default:
|
|
/* EBADF, EINVAL */
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DBG("sending through FD#%d; to send: %zd.\n", sockfd, still);
|
|
if ((sent = sendto(sockfd, pos, still, MSG_DONTWAIT|MSG_NOSIGNAL,
|
|
saddr, saddrlen)) < 0) {
|
|
switch (errno) {
|
|
/* transient errors */
|
|
case EAGAIN: /* would block - strange one */
|
|
#if EAGAIN != EWOULDBLOCK
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
WARN("sendto failed with EAGAIN|EWOULDBLOCK after "
|
|
"select.\n");
|
|
case EINTR: /* signal delivered */
|
|
case ENOBUFS:
|
|
case ENOMEM:
|
|
case ENETDOWN:
|
|
case ENETUNREACH:
|
|
continue;
|
|
|
|
case EMSGSIZE:
|
|
if (offt)
|
|
/* should never happen */
|
|
WERRNO(EINPROGRESS);
|
|
else
|
|
WSYSERRNO;
|
|
return false;
|
|
|
|
default:
|
|
WSYSERRNO;
|
|
return false;
|
|
}
|
|
} else {
|
|
pos += sent;
|
|
still -= sent;
|
|
}
|
|
}
|
|
|
|
DBG("full message buffer sent.\n");
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Error codes:
|
|
* EMSGSIZE: something was read (header) but buffer would not accomodate
|
|
* message.
|
|
*/
|
|
brpc_t *brpc_recvfrom(int sockfd, brpc_addr_t *src, brpc_tv_t tout)
|
|
{
|
|
/* can not use larger static as it could be called by threads */
|
|
/* TODO: if no multithreaded => static, larger */
|
|
uint8_t buff[BINRPC_MAX_PKT_LEN], *pos;
|
|
ssize_t rcvd;
|
|
brpc_tv_t now, prev;
|
|
ssize_t offt;
|
|
size_t msglen, buff_need;
|
|
ssize_t have_len /*flag*/;
|
|
struct timeval tv;
|
|
fd_set rset;
|
|
struct sockaddr *saddr;
|
|
socklen_t *addrlen;
|
|
int socktype;
|
|
socklen_t optlen;
|
|
brpc_t *msg;
|
|
|
|
if (src) {
|
|
saddr = (struct sockaddr *)&src->sockaddr;
|
|
addrlen = &src->addrlen;
|
|
socktype = BRPC_ADDR_TYPE(src);
|
|
switch (socktype) {
|
|
case PF_LOCAL: *addrlen = sizeof(struct sockaddr_un);
|
|
case PF_INET: *addrlen = sizeof(struct sockaddr_in);
|
|
case PF_INET6: *addrlen = sizeof(struct sockaddr_in6);
|
|
}
|
|
} else {
|
|
saddr = NULL;
|
|
addrlen = NULL;
|
|
optlen = sizeof(socktype);
|
|
if (getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype,
|
|
&optlen) < 0) {
|
|
WSYSERRNO;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* on streams we can only read up to current message, while we have to
|
|
* engulf the whole datagram from one shot */
|
|
msglen = (socktype == SOCK_DGRAM) ? BINRPC_MAX_PKT_LEN : MIN_PKT_LEN;
|
|
offt = 0;
|
|
have_len = -1;
|
|
pos = buff;
|
|
prev = tout ? brpc_now() : 0;
|
|
do {
|
|
#ifdef NET_DEBUG
|
|
DBG("receiving through FD#%d; rcvd: %zd; room for: %zd.\n", sockfd,
|
|
offt, msglen - offt);
|
|
#endif
|
|
if (0 < (rcvd = recvfrom(sockfd, pos, msglen - offt,
|
|
MSG_DONTWAIT|MSG_NOSIGNAL, saddr, addrlen))) {
|
|
pos += rcvd;
|
|
offt += rcvd;
|
|
|
|
if (have_len < 0) {
|
|
have_len = brpc_pkt_len(buff, offt);
|
|
if (have_len < 0) { /* need to read more from header */
|
|
if (socktype == SOCK_DGRAM)
|
|
goto errdgram;
|
|
buff_need = offt + (-have_len);
|
|
if (msglen < buff_need)
|
|
msglen = buff_need;
|
|
} else if (BINRPC_MAX_PKT_LEN < have_len) {
|
|
/* TODO: try to read all the packet if still have time,
|
|
* so that the socket remain usable */
|
|
WERRNO(EMSGSIZE);
|
|
return NULL;
|
|
} else {
|
|
msglen = have_len;
|
|
if ((socktype == SOCK_DGRAM) && (msglen != rcvd))
|
|
goto errdgram;
|
|
}
|
|
}
|
|
} else {
|
|
switch (errno) {
|
|
case 0:
|
|
if (rcvd == 0) { /* conn closed */
|
|
WERRNO(ECONNRESET);
|
|
return NULL;
|
|
}
|
|
/* should never happen */
|
|
BUG("recvfrom returned %zd, but no error signaled.\n",
|
|
rcvd);
|
|
abort();
|
|
case ENOBUFS:
|
|
case ENOMEM: /* TODO: does this realy make sense? */
|
|
case EINTR:
|
|
break;
|
|
default:
|
|
WSYSERRNO;
|
|
return NULL;
|
|
|
|
case EAGAIN:
|
|
#if EAGAIN != EWOULDBLOCK
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
eagain:
|
|
FD_ZERO(&rset);
|
|
FD_SET(sockfd, &rset);
|
|
if (prev) {
|
|
now = brpc_now();
|
|
tout -= now - prev;
|
|
prev = now;
|
|
tv.tv_sec = tout / 1000000LU;
|
|
tv.tv_usec = tout - (tv.tv_sec * 1000000LU);
|
|
#ifdef NET_DEBUG
|
|
DBG("select timer armed (tout: %lu).\n", tout);
|
|
#endif
|
|
}
|
|
switch (select(sockfd + 1, &rset, NULL, NULL,
|
|
prev ? &tv : NULL)) {
|
|
case 0:
|
|
if (offt)
|
|
WERRNO(EINPROGRESS);
|
|
else
|
|
WERRNO(ETIMEDOUT);
|
|
return NULL;
|
|
case 1:
|
|
assert(FD_ISSET(sockfd, &rset));
|
|
break;
|
|
default:
|
|
switch (errno) {
|
|
case EINTR:
|
|
goto eagain;
|
|
default:
|
|
WSYSERRNO;
|
|
return NULL;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
errno = 0;
|
|
continue; /* the condition below will fail for sure */
|
|
}
|
|
} while (offt < msglen);
|
|
|
|
pos = (uint8_t *)brpc_malloc(msglen * sizeof(uint8_t));
|
|
if (! pos) {
|
|
WERRNO(ENOMEM);
|
|
return NULL;
|
|
}
|
|
memcpy(pos, buff, msglen);
|
|
msg = brpc_raw(pos, msglen);
|
|
if (! msg) {
|
|
brpc_free(pos);
|
|
return NULL;
|
|
}
|
|
return msg;
|
|
|
|
errdgram:
|
|
ERR("failed to read complete message in datagram.\n");
|
|
WERRNO(EMSGSIZE);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Read available data from peer.
|
|
* @param state BINRPC read state.
|
|
* @return State of operation: true - something was read; system error, if
|
|
* error occurs.
|
|
*/
|
|
bool brpc_strd_read(brpc_strd_t *state)
|
|
{
|
|
ssize_t rcvd;
|
|
|
|
restart:
|
|
rcvd = read(state->fd, state->buff + state->offset,
|
|
sizeof(state->buff) - state->offset);
|
|
DBG("r_reading: offset: %zd, room for: %zd; read: %zd.\n", state->offset,
|
|
sizeof(state->buff) - state->offset, rcvd);
|
|
|
|
if (0 < rcvd) {
|
|
state->offset += rcvd;
|
|
return true;
|
|
}
|
|
|
|
if (rcvd == 0) {
|
|
DBG("connection shut down while reading descriptor %d.\n", state->fd);
|
|
} else {
|
|
if (errno == EINTR)
|
|
goto restart;
|
|
WSYSERRNO;
|
|
DBG("read (transient?) error (%d: %s) while reading descriptor %d.\n",
|
|
brpc_errno, brpc_strerror(), state->fd);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get buffer holding a BINRPC packet.
|
|
* @param state BINRPC read state
|
|
* @param len Output parameter: lenght of packet
|
|
* @return Packet buffer; NULL on error; EMSGSIZE may be set if packet to
|
|
* large.
|
|
*/
|
|
uint8_t *brpc_strd_wirepkt(brpc_strd_t *state, size_t *len)
|
|
{
|
|
uint8_t *pktbuf;
|
|
|
|
if (state->pkt_len < 0) {
|
|
state->pkt_len = brpc_pkt_len(state->buff, state->offset);
|
|
if (state->pkt_len < 0)
|
|
return NULL; /* complete header not yet ready */
|
|
else if (BINRPC_MAX_PKT_LEN < state->pkt_len) {
|
|
/* TODO: try recover (=skip pkt_len bytes)?? */
|
|
ERR("packet too large: %zd while max supported is %zd.\n",
|
|
state->pkt_len, BINRPC_MAX_PKT_LEN);
|
|
WERRNO(EMSGSIZE);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (state->offset < state->pkt_len)
|
|
return NULL; /* complete packet not yet read */
|
|
|
|
if (! (pktbuf = (uint8_t *)brpc_malloc(state->pkt_len * sizeof(uint8_t)))){
|
|
WERRNO(ENOMEM);
|
|
return NULL;
|
|
}
|
|
*len = state->pkt_len;
|
|
memcpy(pktbuf, state->buff, state->pkt_len);
|
|
/* fix state: recycle buffer space, fix offset & pkt length */
|
|
state->offset -= state->pkt_len;
|
|
memcpy(state->buff, state->buff + state->pkt_len, state->offset);
|
|
state->pkt_len = -MIN_PKT_LEN;
|
|
return pktbuf;
|
|
}
|