1378 lines
25 KiB
C
1378 lines
25 KiB
C
/**
|
|
* @file tcp.c Transport Control Protocol
|
|
*
|
|
* Copyright (C) 2010 Creytiv.com
|
|
*/
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_IO_H
|
|
#include <io.h>
|
|
#endif
|
|
#if !defined(WIN32)
|
|
#define __USE_POSIX 1 /**< Use POSIX flag */
|
|
#define __USE_XOPEN2K 1/**< Use POSIX.1:2001 code */
|
|
#define __USE_MISC 1
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef __APPLE__
|
|
#include "TargetConditionals.h"
|
|
#endif
|
|
#include <string.h>
|
|
#include <re_types.h>
|
|
#include <re_fmt.h>
|
|
#include <re_mem.h>
|
|
#include <re_mbuf.h>
|
|
#include <re_list.h>
|
|
#include <re_main.h>
|
|
#include <re_sa.h>
|
|
#include <re_net.h>
|
|
#include <re_tcp.h>
|
|
|
|
|
|
#define DEBUG_MODULE "tcp"
|
|
#define DEBUG_LEVEL 5
|
|
#include <re_dbg.h>
|
|
|
|
|
|
/** Platform independent buffer type cast */
|
|
#ifdef WIN32
|
|
#define BUF_CAST (char *)
|
|
#define SOK_CAST (int)
|
|
#define SIZ_CAST (int)
|
|
#define close closesocket
|
|
#else
|
|
#define BUF_CAST
|
|
#define SOK_CAST
|
|
#define SIZ_CAST
|
|
#endif
|
|
|
|
|
|
enum {
|
|
TCP_TXQSZ_DEFAULT = 524288,
|
|
TCP_RXSZ_DEFAULT = 8192
|
|
};
|
|
|
|
|
|
/** Defines a listening TCP socket */
|
|
struct tcp_sock {
|
|
int fd; /**< Listening file descriptor */
|
|
int fdc; /**< Cached connection file descriptor */
|
|
tcp_conn_h *connh; /**< TCP Connect handler */
|
|
void *arg; /**< Handler argument */
|
|
};
|
|
|
|
|
|
/** Defines a TCP connection */
|
|
struct tcp_conn {
|
|
struct list helpers; /**< List of TCP-helpers */
|
|
struct list sendq; /**< Sending queue */
|
|
int fdc; /**< Connection file descriptor */
|
|
tcp_estab_h *estabh; /**< Connection established handler */
|
|
tcp_send_h *sendh; /**< Data send handler */
|
|
tcp_recv_h *recvh; /**< Data receive handler */
|
|
tcp_close_h *closeh; /**< Connection close handler */
|
|
void *arg; /**< Handler argument */
|
|
size_t rxsz; /**< Maximum receive chunk size */
|
|
size_t txqsz;
|
|
size_t txqsz_max;
|
|
bool active; /**< We are connecting flag */
|
|
bool connected; /**< Connection is connected flag */
|
|
};
|
|
|
|
|
|
/** Defines a TCP-Connection Helper */
|
|
struct tcp_helper {
|
|
struct le le;
|
|
int layer;
|
|
tcp_helper_estab_h *estabh;
|
|
tcp_helper_send_h *sendh;
|
|
tcp_helper_recv_h *recvh;
|
|
void *arg;
|
|
};
|
|
|
|
|
|
struct tcp_qent {
|
|
struct le le;
|
|
struct mbuf mb;
|
|
};
|
|
|
|
|
|
static void tcp_recv_handler(int flags, void *arg);
|
|
|
|
|
|
static bool helper_estab_handler(int *err, bool active, void *arg)
|
|
{
|
|
(void)err;
|
|
(void)active;
|
|
(void)arg;
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool helper_send_handler(int *err, struct mbuf *mb, void *arg)
|
|
{
|
|
(void)err;
|
|
(void)mb;
|
|
(void)arg;
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool helper_recv_handler(int *err, struct mbuf *mb, bool *estab,
|
|
void *arg)
|
|
{
|
|
(void)err;
|
|
(void)mb;
|
|
(void)estab;
|
|
(void)arg;
|
|
return false;
|
|
}
|
|
|
|
|
|
static void sock_destructor(void *data)
|
|
{
|
|
struct tcp_sock *ts = data;
|
|
|
|
if (ts->fd >= 0) {
|
|
fd_close(ts->fd);
|
|
(void)close(ts->fd);
|
|
}
|
|
if (ts->fdc >= 0)
|
|
(void)close(ts->fdc);
|
|
}
|
|
|
|
|
|
static void conn_destructor(void *data)
|
|
{
|
|
struct tcp_conn *tc = data;
|
|
|
|
list_flush(&tc->helpers);
|
|
list_flush(&tc->sendq);
|
|
|
|
if (tc->fdc >= 0) {
|
|
fd_close(tc->fdc);
|
|
(void)close(tc->fdc);
|
|
}
|
|
}
|
|
|
|
|
|
static void helper_destructor(void *data)
|
|
{
|
|
struct tcp_helper *th = data;
|
|
|
|
list_unlink(&th->le);
|
|
}
|
|
|
|
|
|
static void qent_destructor(void *arg)
|
|
{
|
|
struct tcp_qent *qe = arg;
|
|
|
|
list_unlink(&qe->le);
|
|
mem_deref(qe->mb.buf);
|
|
}
|
|
|
|
|
|
static int enqueue(struct tcp_conn *tc, struct mbuf *mb)
|
|
{
|
|
const size_t n = mbuf_get_left(mb);
|
|
struct tcp_qent *qe;
|
|
int err;
|
|
|
|
if (tc->txqsz + n > tc->txqsz_max)
|
|
return ENOSPC;
|
|
|
|
if (!tc->sendq.head && !tc->sendh) {
|
|
|
|
err = fd_listen(tc->fdc, FD_READ | FD_WRITE,
|
|
tcp_recv_handler, tc);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
qe = mem_zalloc(sizeof(*qe), qent_destructor);
|
|
if (!qe)
|
|
return ENOMEM;
|
|
|
|
list_append(&tc->sendq, &qe->le, qe);
|
|
|
|
mbuf_init(&qe->mb);
|
|
|
|
err = mbuf_write_mem(&qe->mb, mbuf_buf(mb), n);
|
|
qe->mb.pos = 0;
|
|
|
|
if (err)
|
|
mem_deref(qe);
|
|
else
|
|
tc->txqsz += qe->mb.end;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int dequeue(struct tcp_conn *tc)
|
|
{
|
|
struct tcp_qent *qe = list_ledata(tc->sendq.head);
|
|
ssize_t n;
|
|
#ifdef MSG_NOSIGNAL
|
|
const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
|
|
#else
|
|
const int flags = 0;
|
|
#endif
|
|
if (!qe) {
|
|
if (tc->sendh)
|
|
tc->sendh(tc->arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
n = send(tc->fdc, BUF_CAST mbuf_buf(&qe->mb),
|
|
qe->mb.end - qe->mb.pos, flags);
|
|
if (n < 0) {
|
|
if (EAGAIN == errno)
|
|
return 0;
|
|
#ifdef WIN32
|
|
if (WSAEWOULDBLOCK == WSAGetLastError())
|
|
return 0;
|
|
#endif
|
|
return errno;
|
|
}
|
|
|
|
tc->txqsz -= n;
|
|
qe->mb.pos += n;
|
|
|
|
if (qe->mb.pos >= qe->mb.end)
|
|
mem_deref(qe);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void conn_close(struct tcp_conn *tc, int err)
|
|
{
|
|
list_flush(&tc->sendq);
|
|
tc->txqsz = 0;
|
|
|
|
/* Stop polling */
|
|
if (tc->fdc >= 0) {
|
|
fd_close(tc->fdc);
|
|
(void)close(tc->fdc);
|
|
tc->fdc = -1;
|
|
}
|
|
|
|
if (tc->closeh)
|
|
tc->closeh(err, tc->arg);
|
|
}
|
|
|
|
|
|
static void tcp_recv_handler(int flags, void *arg)
|
|
{
|
|
struct tcp_conn *tc = arg;
|
|
struct mbuf *mb = NULL;
|
|
bool hlp_estab = false;
|
|
struct le *le;
|
|
ssize_t n;
|
|
int err;
|
|
socklen_t err_len = sizeof(err);
|
|
|
|
if (flags & FD_EXCEPT) {
|
|
DEBUG_INFO("recv handler: got FD_EXCEPT on fd=%d\n", tc->fdc);
|
|
}
|
|
|
|
/* check for any errors */
|
|
if (-1 == getsockopt(tc->fdc, SOL_SOCKET, SO_ERROR,
|
|
BUF_CAST &err, &err_len)) {
|
|
DEBUG_WARNING("recv handler: getsockopt: (%m)\n", errno);
|
|
return;
|
|
}
|
|
|
|
if (err) {
|
|
conn_close(tc, err);
|
|
return;
|
|
}
|
|
#if 0
|
|
if (EINPROGRESS != err && EALREADY != err) {
|
|
DEBUG_WARNING("recv handler: Socket error (%m)\n", err);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (flags & FD_WRITE) {
|
|
|
|
if (tc->connected) {
|
|
|
|
uint32_t nrefs;
|
|
|
|
mem_ref(tc);
|
|
|
|
err = dequeue(tc);
|
|
|
|
nrefs = mem_nrefs(tc);
|
|
mem_deref(tc);
|
|
|
|
/* check if connection was deref'd from send handler */
|
|
if (nrefs == 1)
|
|
return;
|
|
|
|
if (err) {
|
|
conn_close(tc, err);
|
|
return;
|
|
}
|
|
|
|
if (!tc->sendq.head && !tc->sendh) {
|
|
|
|
err = fd_listen(tc->fdc, FD_READ,
|
|
tcp_recv_handler, tc);
|
|
if (err) {
|
|
conn_close(tc, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (flags & FD_READ)
|
|
goto read;
|
|
|
|
return;
|
|
}
|
|
|
|
tc->connected = true;
|
|
|
|
err = fd_listen(tc->fdc, FD_READ, tcp_recv_handler, tc);
|
|
if (err) {
|
|
DEBUG_WARNING("recv handler: fd_listen(): %m\n", err);
|
|
conn_close(tc, err);
|
|
return;
|
|
}
|
|
|
|
le = tc->helpers.head;
|
|
while (le) {
|
|
struct tcp_helper *th = le->data;
|
|
|
|
le = le->next;
|
|
|
|
if (th->estabh(&err, tc->active, th->arg) || err) {
|
|
if (err)
|
|
conn_close(tc, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tc->estabh)
|
|
tc->estabh(tc->arg);
|
|
|
|
return;
|
|
}
|
|
|
|
read:
|
|
mb = mbuf_alloc(tc->rxsz);
|
|
if (!mb)
|
|
return;
|
|
|
|
n = recv(tc->fdc, BUF_CAST mb->buf, mb->size, 0);
|
|
if (0 == n) {
|
|
mem_deref(mb);
|
|
conn_close(tc, 0);
|
|
return;
|
|
}
|
|
else if (n < 0) {
|
|
DEBUG_WARNING("recv handler: recv(): %m\n", errno);
|
|
goto out;
|
|
}
|
|
|
|
mb->end = n;
|
|
|
|
le = tc->helpers.head;
|
|
while (le) {
|
|
struct tcp_helper *th = le->data;
|
|
bool hdld = false;
|
|
|
|
le = le->next;
|
|
|
|
if (hlp_estab) {
|
|
|
|
hdld |= th->estabh(&err, tc->active, th->arg);
|
|
if (err) {
|
|
conn_close(tc, err);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (mb->pos < mb->end) {
|
|
|
|
hdld |= th->recvh(&err, mb, &hlp_estab, th->arg);
|
|
if (err) {
|
|
conn_close(tc, err);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (hdld)
|
|
goto out;
|
|
}
|
|
|
|
mbuf_trim(mb);
|
|
|
|
if (hlp_estab && tc->estabh) {
|
|
|
|
uint32_t nrefs;
|
|
|
|
mem_ref(tc);
|
|
|
|
tc->estabh(tc->arg);
|
|
|
|
nrefs = mem_nrefs(tc);
|
|
mem_deref(tc);
|
|
|
|
/* check if connection was deref'ed from establish handler */
|
|
if (nrefs == 1)
|
|
goto out;
|
|
}
|
|
|
|
if (mb->pos < mb->end && tc->recvh) {
|
|
tc->recvh(mb, tc->arg);
|
|
}
|
|
|
|
out:
|
|
mem_deref(mb);
|
|
}
|
|
|
|
|
|
static struct tcp_conn *conn_alloc(tcp_estab_h *eh, tcp_recv_h *rh,
|
|
tcp_close_h *ch, void *arg)
|
|
{
|
|
struct tcp_conn *tc;
|
|
|
|
tc = mem_zalloc(sizeof(*tc), conn_destructor);
|
|
if (!tc)
|
|
return NULL;
|
|
|
|
list_init(&tc->helpers);
|
|
|
|
tc->fdc = -1;
|
|
tc->rxsz = TCP_RXSZ_DEFAULT;
|
|
tc->txqsz_max = TCP_TXQSZ_DEFAULT;
|
|
tc->estabh = eh;
|
|
tc->recvh = rh;
|
|
tc->closeh = ch;
|
|
tc->arg = arg;
|
|
|
|
return tc;
|
|
}
|
|
|
|
|
|
static void tcp_sockopt_set(int fd)
|
|
{
|
|
#ifdef SO_LINGER
|
|
const struct linger dl = {0, 0};
|
|
int err;
|
|
|
|
err = setsockopt(fd, SOL_SOCKET, SO_LINGER, BUF_CAST &dl, sizeof(dl));
|
|
if (err) {
|
|
DEBUG_WARNING("sockopt: SO_LINGER (%m)\n", err);
|
|
}
|
|
#else
|
|
(void)fd;
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Handler for incoming TCP connections.
|
|
*
|
|
* @param flags Event flags.
|
|
* @param arg Handler argument.
|
|
*/
|
|
static void tcp_conn_handler(int flags, void *arg)
|
|
{
|
|
struct sa peer;
|
|
struct tcp_sock *ts = arg;
|
|
int err;
|
|
|
|
(void)flags;
|
|
|
|
sa_init(&peer, AF_UNSPEC);
|
|
|
|
if (ts->fdc >= 0)
|
|
(void)close(ts->fdc);
|
|
|
|
ts->fdc = SOK_CAST accept(ts->fd, &peer.u.sa, &peer.len);
|
|
if (-1 == ts->fdc) {
|
|
|
|
#if TARGET_OS_IPHONE
|
|
if (EAGAIN == errno) {
|
|
|
|
struct tcp_sock *ts_new;
|
|
struct sa laddr;
|
|
|
|
err = tcp_sock_local_get(ts, &laddr);
|
|
if (err)
|
|
return;
|
|
|
|
if (ts->fd >= 0) {
|
|
fd_close(ts->fd);
|
|
(void)close(ts->fd);
|
|
ts->fd = -1;
|
|
}
|
|
|
|
err = tcp_listen(&ts_new, &laddr, NULL, NULL);
|
|
if (err)
|
|
return;
|
|
|
|
ts->fd = ts_new->fd;
|
|
ts_new->fd = -1;
|
|
|
|
mem_deref(ts_new);
|
|
|
|
fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
err = net_sockopt_blocking_set(ts->fdc, false);
|
|
if (err) {
|
|
DEBUG_WARNING("conn handler: nonblock set: %m\n", err);
|
|
(void)close(ts->fdc);
|
|
ts->fdc = -1;
|
|
return;
|
|
}
|
|
|
|
tcp_sockopt_set(ts->fdc);
|
|
|
|
if (ts->connh)
|
|
ts->connh(&peer, ts->arg);
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a TCP Socket
|
|
*
|
|
* @param tsp Pointer to returned TCP Socket
|
|
* @param local Local listen address (NULL for any)
|
|
* @param ch Incoming connection handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local,
|
|
tcp_conn_h *ch, void *arg)
|
|
{
|
|
struct addrinfo hints, *res = NULL, *r;
|
|
char addr[64] = "";
|
|
char serv[6] = "0";
|
|
struct tcp_sock *ts = NULL;
|
|
int error, err;
|
|
|
|
if (!tsp)
|
|
return EINVAL;
|
|
|
|
ts = mem_zalloc(sizeof(*ts), sock_destructor);
|
|
if (!ts)
|
|
return ENOMEM;
|
|
|
|
ts->fd = -1;
|
|
ts->fdc = -1;
|
|
|
|
if (local) {
|
|
(void)re_snprintf(addr, sizeof(addr), "%H",
|
|
sa_print_addr, local);
|
|
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
/* set-up hints structure */
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
|
|
if (error) {
|
|
#ifdef WIN32
|
|
DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n",
|
|
WSAGetLastError());
|
|
#endif
|
|
DEBUG_WARNING("listen: getaddrinfo: %s:%s error=%d (%s)\n",
|
|
addr, serv, error, gai_strerror(error));
|
|
err = EADDRNOTAVAIL;
|
|
goto out;
|
|
}
|
|
|
|
err = EINVAL;
|
|
for (r = res; r; r = r->ai_next) {
|
|
int fd = -1;
|
|
|
|
if (ts->fd >= 0)
|
|
continue;
|
|
|
|
fd = SOK_CAST socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd < 0) {
|
|
err = errno;
|
|
continue;
|
|
}
|
|
|
|
(void)net_sockopt_reuse_set(fd, true);
|
|
|
|
err = net_sockopt_blocking_set(fd, false);
|
|
if (err) {
|
|
DEBUG_WARNING("listen: nonblock set: %m\n", err);
|
|
(void)close(fd);
|
|
continue;
|
|
}
|
|
|
|
tcp_sockopt_set(fd);
|
|
|
|
/* OK */
|
|
ts->fd = fd;
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
if (-1 == ts->fd)
|
|
goto out;
|
|
|
|
ts->connh = ch;
|
|
ts->arg = arg;
|
|
|
|
out:
|
|
if (err)
|
|
mem_deref(ts);
|
|
else
|
|
*tsp = ts;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Bind to a TCP Socket
|
|
*
|
|
* @param ts TCP Socket
|
|
* @param local Local bind address
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local)
|
|
{
|
|
struct addrinfo hints, *res = NULL, *r;
|
|
char addr[64] = "";
|
|
char serv[NI_MAXSERV] = "0";
|
|
int error, err;
|
|
|
|
if (!ts || ts->fd<0)
|
|
return EINVAL;
|
|
|
|
if (local) {
|
|
(void)re_snprintf(addr, sizeof(addr), "%H",
|
|
sa_print_addr, local);
|
|
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
/* set-up hints structure */
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
|
|
if (error) {
|
|
#ifdef WIN32
|
|
DEBUG_WARNING("sock_bind: getaddrinfo: wsaerr=%d\n",
|
|
WSAGetLastError());
|
|
#endif
|
|
DEBUG_WARNING("sock_bind: getaddrinfo: %s:%s error=%d (%s)\n",
|
|
addr, serv, error, gai_strerror(error));
|
|
return EADDRNOTAVAIL;
|
|
}
|
|
|
|
err = EINVAL;
|
|
for (r = res; r; r = r->ai_next) {
|
|
|
|
if (bind(ts->fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
|
|
err = errno;
|
|
DEBUG_WARNING("sock_bind: bind: %m (af=%d, %J)\n",
|
|
err, r->ai_family, local);
|
|
continue;
|
|
}
|
|
|
|
/* OK */
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Listen on a TCP Socket
|
|
*
|
|
* @param ts TCP Socket
|
|
* @param backlog Maximum length the queue of pending connections
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_sock_listen(struct tcp_sock *ts, int backlog)
|
|
{
|
|
int err;
|
|
|
|
if (!ts)
|
|
return EINVAL;
|
|
|
|
if (ts->fd < 0) {
|
|
DEBUG_WARNING("sock_listen: invalid fd\n");
|
|
return EBADF;
|
|
}
|
|
|
|
if (listen(ts->fd, backlog) < 0) {
|
|
err = errno;
|
|
DEBUG_WARNING("sock_listen: listen(): %m\n", err);
|
|
return err;
|
|
}
|
|
|
|
return fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
|
|
}
|
|
|
|
|
|
/**
|
|
* Accept an incoming TCP Connection
|
|
*
|
|
* @param tcp Returned TCP Connection object
|
|
* @param ts Corresponding TCP Socket
|
|
* @param eh TCP Connection Established handler
|
|
* @param rh TCP Connection Receive data handler
|
|
* @param ch TCP Connection close handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh,
|
|
tcp_recv_h *rh, tcp_close_h *ch, void *arg)
|
|
{
|
|
struct tcp_conn *tc;
|
|
int err;
|
|
|
|
if (!tcp || !ts || ts->fdc < 0)
|
|
return EINVAL;
|
|
|
|
tc = conn_alloc(eh, rh, ch, arg);
|
|
if (!tc)
|
|
return ENOMEM;
|
|
|
|
/* Transfer ownership to TCP connection */
|
|
tc->fdc = ts->fdc;
|
|
ts->fdc = -1;
|
|
|
|
err = fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
|
|
tcp_recv_handler, tc);
|
|
if (err) {
|
|
DEBUG_WARNING("accept: fd_listen(): %m\n", err);
|
|
}
|
|
|
|
if (err)
|
|
mem_deref(tc);
|
|
else
|
|
*tcp = tc;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reject an incoming TCP Connection
|
|
*
|
|
* @param ts Corresponding TCP Socket
|
|
*/
|
|
void tcp_reject(struct tcp_sock *ts)
|
|
{
|
|
if (!ts)
|
|
return;
|
|
|
|
if (ts->fdc >= 0) {
|
|
(void)close(ts->fdc);
|
|
ts->fdc = -1;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Allocate a TCP Connection
|
|
*
|
|
* @param tcp Returned TCP Connection object
|
|
* @param peer Network address of peer
|
|
* @param eh TCP Connection Established handler
|
|
* @param rh TCP Connection Receive data handler
|
|
* @param ch TCP Connection close handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_conn_alloc(struct tcp_conn **tcp,
|
|
const struct sa *peer, tcp_estab_h *eh,
|
|
tcp_recv_h *rh, tcp_close_h *ch, void *arg)
|
|
{
|
|
struct tcp_conn *tc;
|
|
struct addrinfo hints, *res = NULL, *r;
|
|
char addr[64];
|
|
char serv[NI_MAXSERV] = "0";
|
|
int error, err;
|
|
|
|
if (!tcp || !sa_isset(peer, SA_ALL))
|
|
return EINVAL;
|
|
|
|
tc = conn_alloc(eh, rh, ch, arg);
|
|
if (!tc)
|
|
return ENOMEM;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
/* set-up hints structure */
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
(void)re_snprintf(addr, sizeof(addr), "%H",
|
|
sa_print_addr, peer);
|
|
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
|
|
|
|
error = getaddrinfo(addr, serv, &hints, &res);
|
|
if (error) {
|
|
DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
|
|
gai_strerror(error));
|
|
err = EADDRNOTAVAIL;
|
|
goto out;
|
|
}
|
|
|
|
err = EINVAL;
|
|
for (r = res; r; r = r->ai_next) {
|
|
|
|
tc->fdc = SOK_CAST socket(r->ai_family, SOCK_STREAM,
|
|
IPPROTO_TCP);
|
|
if (tc->fdc < 0) {
|
|
err = errno;
|
|
continue;
|
|
}
|
|
|
|
err = net_sockopt_blocking_set(tc->fdc, false);
|
|
if (err) {
|
|
DEBUG_WARNING("connect: nonblock set: %m\n", err);
|
|
(void)close(tc->fdc);
|
|
tc->fdc = -1;
|
|
continue;
|
|
}
|
|
|
|
tcp_sockopt_set(tc->fdc);
|
|
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
out:
|
|
if (err)
|
|
mem_deref(tc);
|
|
else
|
|
*tcp = tc;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Bind a TCP Connection to a local address
|
|
*
|
|
* @param tc TCP Connection object
|
|
* @param local Local bind address
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local)
|
|
{
|
|
struct addrinfo hints, *res = NULL, *r;
|
|
char addr[64] = "";
|
|
char serv[NI_MAXSERV] = "0";
|
|
int error, err;
|
|
|
|
if (!tc)
|
|
return EINVAL;
|
|
|
|
if (local) {
|
|
(void)re_snprintf(addr, sizeof(addr), "%H",
|
|
sa_print_addr, local);
|
|
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
/* set-up hints structure */
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
|
|
if (error) {
|
|
DEBUG_WARNING("conn_bind: getaddrinfo(): (%s)\n",
|
|
gai_strerror(error));
|
|
return EADDRNOTAVAIL;
|
|
}
|
|
|
|
err = EINVAL;
|
|
for (r = res; r; r = r->ai_next) {
|
|
|
|
(void)net_sockopt_reuse_set(tc->fdc, true);
|
|
|
|
/* bind to local address */
|
|
if (bind(tc->fdc, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
|
|
|
|
/* Special case for mingw32/wine */
|
|
if (0 == errno) {
|
|
goto ok;
|
|
}
|
|
|
|
err = errno;
|
|
DEBUG_WARNING("conn_bind: bind(): %J: %m\n",
|
|
local, err);
|
|
continue;
|
|
}
|
|
|
|
ok:
|
|
/* OK */
|
|
err = 0;
|
|
break;
|
|
}
|
|
|
|
freeaddrinfo(res);
|
|
|
|
if (err) {
|
|
DEBUG_WARNING("conn_bind failed: %J (%m)\n", local, err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* Connect to a remote peer
|
|
*
|
|
* @param tc TCP Connection object
|
|
* @param peer Network address of peer
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer)
|
|
{
|
|
struct addrinfo hints, *res = NULL, *r;
|
|
char addr[64];
|
|
char serv[NI_MAXSERV];
|
|
int error, err = 0;
|
|
|
|
if (!tc || !sa_isset(peer, SA_ALL))
|
|
return EINVAL;
|
|
|
|
tc->active = true;
|
|
|
|
if (tc->fdc < 0) {
|
|
DEBUG_WARNING("invalid fd\n");
|
|
return EBADF;
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
/* set-up hints structure */
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
|
|
(void)re_snprintf(addr, sizeof(addr), "%H",
|
|
sa_print_addr, peer);
|
|
(void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
|
|
|
|
error = getaddrinfo(addr, serv, &hints, &res);
|
|
if (error) {
|
|
DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
|
|
gai_strerror(error));
|
|
return EADDRNOTAVAIL;
|
|
}
|
|
|
|
for (r = res; r; r = r->ai_next) {
|
|
struct sockaddr *sa = r->ai_addr;
|
|
|
|
again:
|
|
if (0 == connect(tc->fdc, sa, SIZ_CAST r->ai_addrlen)) {
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
else {
|
|
#ifdef WIN32
|
|
/* Special error handling for Windows */
|
|
if (WSAEWOULDBLOCK == WSAGetLastError()) {
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
/* Special case for mingw32/wine */
|
|
if (0 == errno) {
|
|
err = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (EINTR == errno)
|
|
goto again;
|
|
|
|
if (EINPROGRESS != errno && EALREADY != errno) {
|
|
err = errno;
|
|
DEBUG_INFO("connect: connect() %J: %m\n",
|
|
peer, err);
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
freeaddrinfo(res);
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
return fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
|
|
tcp_recv_handler, tc);
|
|
}
|
|
|
|
|
|
static int tcp_send_internal(struct tcp_conn *tc, struct mbuf *mb,
|
|
struct le *le)
|
|
{
|
|
int err = 0;
|
|
ssize_t n;
|
|
#ifdef MSG_NOSIGNAL
|
|
const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
|
|
#else
|
|
const int flags = 0;
|
|
#endif
|
|
|
|
if (tc->fdc < 0)
|
|
return ENOTCONN;
|
|
|
|
if (!mbuf_get_left(mb)) {
|
|
DEBUG_WARNING("send: empty mbuf (pos=%u end=%u)\n",
|
|
mb->pos, mb->end);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* call helpers in reverse order */
|
|
while (le) {
|
|
struct tcp_helper *th = le->data;
|
|
|
|
le = le->prev;
|
|
|
|
if (th->sendh(&err, mb, th->arg) || err)
|
|
return err;
|
|
}
|
|
|
|
if (tc->sendq.head)
|
|
return enqueue(tc, mb);
|
|
|
|
n = send(tc->fdc, BUF_CAST mbuf_buf(mb), mb->end - mb->pos, flags);
|
|
if (n < 0) {
|
|
|
|
if (EAGAIN == errno)
|
|
return enqueue(tc, mb);
|
|
|
|
#ifdef WIN32
|
|
if (WSAEWOULDBLOCK == WSAGetLastError())
|
|
return enqueue(tc, mb);
|
|
#endif
|
|
err = errno;
|
|
|
|
DEBUG_WARNING("send: write(): %m (fdc=%d)\n", err, tc->fdc);
|
|
|
|
#ifdef WIN32
|
|
DEBUG_WARNING("WIN32 error: %d\n", WSAGetLastError());
|
|
#endif
|
|
|
|
return err;
|
|
}
|
|
|
|
if ((size_t)n < mb->end - mb->pos) {
|
|
|
|
mb->pos += n;
|
|
err = enqueue(tc, mb);
|
|
mb->pos -= n;
|
|
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Send data on a TCP Connection to a remote peer
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param mb Buffer to send
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_send(struct tcp_conn *tc, struct mbuf *mb)
|
|
{
|
|
if (!tc || !mb)
|
|
return EINVAL;
|
|
|
|
return tcp_send_internal(tc, mb, tc->helpers.tail);
|
|
}
|
|
|
|
|
|
/**
|
|
* Send data on a TCP Connection to a remote peer bypassing this
|
|
* helper and the helpers above it.
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param mb Buffer to send
|
|
* @param th TCP Helper
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb,
|
|
struct tcp_helper *th)
|
|
{
|
|
if (!tc || !mb || !th)
|
|
return EINVAL;
|
|
|
|
return tcp_send_internal(tc, mb, th->le.prev);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the send handler on a TCP Connection, which will be called
|
|
* every time it is ready to send data
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param sendh TCP Send handler
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh)
|
|
{
|
|
if (!tc)
|
|
return EINVAL;
|
|
|
|
tc->sendh = sendh;
|
|
|
|
if (tc->sendq.head || !sendh)
|
|
return 0;
|
|
|
|
return fd_listen(tc->fdc, FD_READ | FD_WRITE, tcp_recv_handler, tc);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set handlers on a TCP Connection
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param eh TCP Connection Established handler
|
|
* @param rh TCP Connection Receive data handler
|
|
* @param ch TCP Connection Close handler
|
|
* @param arg Handler argument
|
|
*/
|
|
void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh,
|
|
tcp_close_h *ch, void *arg)
|
|
{
|
|
if (!tc)
|
|
return;
|
|
|
|
tc->estabh = eh;
|
|
tc->recvh = rh;
|
|
tc->closeh = ch;
|
|
tc->arg = arg;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get local network address of TCP Socket
|
|
*
|
|
* @param ts TCP Socket
|
|
* @param local Returned local network address
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local)
|
|
{
|
|
if (!ts || !local)
|
|
return EINVAL;
|
|
|
|
sa_init(local, AF_UNSPEC);
|
|
|
|
if (getsockname(ts->fd, &local->u.sa, &local->len) < 0) {
|
|
DEBUG_WARNING("local get: getsockname(): %m\n", errno);
|
|
return errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get local network address of TCP Connection
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param local Returned local network address
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local)
|
|
{
|
|
if (!tc || !local)
|
|
return EINVAL;
|
|
|
|
sa_init(local, AF_UNSPEC);
|
|
|
|
if (getsockname(tc->fdc, &local->u.sa, &local->len) < 0) {
|
|
DEBUG_WARNING("conn local get: getsockname(): %m\n", errno);
|
|
return errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get remote peer network address of TCP Connection
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param peer Returned remote peer network address
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer)
|
|
{
|
|
if (!tc || !peer)
|
|
return EINVAL;
|
|
|
|
sa_init(peer, AF_UNSPEC);
|
|
|
|
if (getpeername(tc->fdc, &peer->u.sa, &peer->len) < 0) {
|
|
DEBUG_WARNING("conn peer get: getpeername(): %m\n", errno);
|
|
return errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the maximum receive chunk size on a TCP Connection
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param rxsz Maximum receive chunk size
|
|
*/
|
|
void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz)
|
|
{
|
|
if (!tc)
|
|
return;
|
|
|
|
tc->rxsz = rxsz;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the maximum send queue size on a TCP Connection
|
|
*
|
|
* @param tc TCP Connection
|
|
* @param txqsz Maximum send queue size
|
|
*/
|
|
void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz)
|
|
{
|
|
if (!tc)
|
|
return;
|
|
|
|
tc->txqsz_max = txqsz;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the file descriptor of a TCP Connection
|
|
*
|
|
* @param tc TCP-Connection
|
|
*
|
|
* @return File destriptor, or -1 if errors
|
|
*/
|
|
int tcp_conn_fd(const struct tcp_conn *tc)
|
|
{
|
|
return tc ? tc->fdc : -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the current length of the transmit queue on a TCP Connection
|
|
*
|
|
* @param tc TCP-Connection
|
|
*
|
|
* @return Current transmit queue length, or 0 if errors
|
|
*/
|
|
size_t tcp_conn_txqsz(const struct tcp_conn *tc)
|
|
{
|
|
return tc ? tc->txqsz : 0;
|
|
}
|
|
|
|
|
|
static bool sort_handler(struct le *le1, struct le *le2, void *arg)
|
|
{
|
|
struct tcp_helper *th1 = le1->data, *th2 = le2->data;
|
|
(void)arg;
|
|
|
|
return th1->layer <= th2->layer;
|
|
}
|
|
|
|
|
|
/**
|
|
* Register a new TCP-helper on a TCP-Connection
|
|
*
|
|
* @param thp Pointer to allocated TCP helper
|
|
* @param tc TCP Connection
|
|
* @param layer Protocol layer; higher number means higher up in stack
|
|
* @param eh Established handler
|
|
* @param sh Send handler
|
|
* @param rh Receive handler
|
|
* @param arg Handler argument
|
|
*
|
|
* @return 0 if success, otherwise errorcode
|
|
*/
|
|
int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc,
|
|
int layer,
|
|
tcp_helper_estab_h *eh, tcp_helper_send_h *sh,
|
|
tcp_helper_recv_h *rh, void *arg)
|
|
{
|
|
struct tcp_helper *th;
|
|
|
|
if (!tc)
|
|
return EINVAL;
|
|
|
|
th = mem_zalloc(sizeof(*th), helper_destructor);
|
|
if (!th)
|
|
return ENOMEM;
|
|
|
|
list_append(&tc->helpers, &th->le, th);
|
|
|
|
th->layer = layer;
|
|
th->estabh = eh ? eh : helper_estab_handler;
|
|
th->sendh = sh ? sh : helper_send_handler;
|
|
th->recvh = rh ? rh : helper_recv_handler;
|
|
th->arg = arg;
|
|
|
|
list_sort(&tc->helpers, sort_handler, NULL);
|
|
|
|
if (thp)
|
|
*thp = th;
|
|
|
|
return 0;
|
|
}
|