socat/xio-socket.c
2024-08-05 08:49:35 +02:00

2328 lines
74 KiB
C

/* source: xio-socket.c */
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the source for socket related functions, and the
implementation of generic socket addresses */
#include "xiosysincludes.h"
#if _WITH_SOCKET
#include "xioopen.h"
#include "xio-ascii.h"
#include "xio-socket.h"
#include "xio-named.h"
#include "xio-unix.h"
#if WITH_VSOCK
#include "xio-vsock.h"
#endif
#if WITH_IP4
#include "xio-ip4.h"
#endif /* WITH_IP4 */
#if WITH_IP6
#include "xio-ip6.h"
#endif /* WITH_IP6 */
#include "xio-ip.h"
#include "xio-listen.h"
#include "xio-interface.h"
#include "xio-ipapp.h" /*! not clean */
#include "xio-tcpwrap.h"
static int xioopen_socket_connect(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static int xioopen_socket_listen(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static int xioopen_socket_sendto(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static int xioopen_socket_datagram(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static int xioopen_socket_recvfrom(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static int xioopen_socket_recv(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc);
static
int _xioopen_socket_sendto(const char *pfname, const char *type,
const char *proto, const char *address,
struct opt *opts, int xioflags, xiofile_t *xxfd,
groups_t groups);
static int
xiolog_ancillary_socket(struct single *sfd, struct cmsghdr *cmsg, int *num,
char *typbuff, int typlen,
char *nambuff, int namlen,
char *envbuff, int envlen,
char *valbuff, int vallen);
static int xiobind(
struct single *sfd,
union sockaddr_union *us,
size_t uslen,
struct opt *opts,
int pf,
bool alt,
int level);
#if WITH_GENERICSOCKET
/* generic socket addresses */
const struct addrdesc xioaddr_socket_connect = { "SOCKET-CONNECT", 1+XIO_RDWR, xioopen_socket_connect, GROUP_FD|GROUP_SOCKET|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":<domain>:<protocol>:<remote-address>") };
#if WITH_LISTEN
const struct addrdesc xioaddr_socket_listen = { "SOCKET-LISTEN", 1+XIO_RDWR, xioopen_socket_listen, GROUP_FD|GROUP_SOCKET|GROUP_LISTEN|GROUP_RANGE|GROUP_CHILD|GROUP_RETRY, 0, 0, 0 HELP(":<domain>:<protocol>:<local-address>") };
#endif /* WITH_LISTEN */
const struct addrdesc xioaddr_socket_sendto = { "SOCKET-SENDTO", 1+XIO_RDWR, xioopen_socket_sendto, GROUP_FD|GROUP_SOCKET, 0, 0, 0 HELP(":<domain>:<type>:<protocol>:<remote-address>") };
const struct addrdesc xioaddr_socket_datagram= { "SOCKET-DATAGRAM", 1+XIO_RDWR, xioopen_socket_datagram, GROUP_FD|GROUP_SOCKET|GROUP_RANGE, 0, 0, 0 HELP(":<domain>:<type>:<protocol>:<remote-address>") };
const struct addrdesc xioaddr_socket_recvfrom= { "SOCKET-RECVFROM", 1+XIO_RDWR, xioopen_socket_recvfrom, GROUP_FD|GROUP_SOCKET|GROUP_RANGE|GROUP_CHILD, 0, 0, 0 HELP(":<domain>:<type>:<protocol>:<local-address>") };
const struct addrdesc xioaddr_socket_recv = { "SOCKET-RECV", 1+XIO_RDONLY, xioopen_socket_recv, GROUP_FD|GROUP_SOCKET|GROUP_RANGE, 0, 0, 0 HELP(":<domain>:<type>:<protocol>:<local-address>") };
#endif /* WITH_GENERICSOCKET */
/* the following options apply not only to generic socket addresses but to all
addresses that have anything to do with sockets */
const struct optdesc opt_so_debug = { "so-debug", "debug", OPT_SO_DEBUG, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_DEBUG };
#ifdef SO_ACCEPTCONN /* AIX433 */
const struct optdesc opt_so_acceptconn={ "so-acceptconn","acceptconn",OPT_SO_ACCEPTCONN,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_ACCEPTCONN};
#endif /* SO_ACCEPTCONN */
const struct optdesc opt_so_broadcast= { "so-broadcast", "broadcast", OPT_SO_BROADCAST,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_BROADCAST};
const struct optdesc opt_so_reuseaddr= { "so-reuseaddr", "reuseaddr", OPT_SO_REUSEADDR,GROUP_SOCKET, PH_PREBIND, TYPE_INT_NULL, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEADDR};
const struct optdesc opt_so_keepalive= { "so-keepalive", "keepalive", OPT_SO_KEEPALIVE,GROUP_SOCKET, PH_FD, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_KEEPALIVE};
#if HAVE_STRUCT_LINGER
const struct optdesc opt_so_linger = { "so-linger", "linger", OPT_SO_LINGER, GROUP_SOCKET, PH_PASTSOCKET, TYPE_LINGER,OFUNC_SOCKOPT,SOL_SOCKET, SO_LINGER };
#else /* !HAVE_STRUCT_LINGER */
const struct optdesc opt_so_linger = { "so-linger", "linger", OPT_SO_LINGER, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_LINGER };
#endif /* !HAVE_STRUCT_LINGER */
const struct optdesc opt_so_oobinline= { "so-oobinline", "oobinline", OPT_SO_OOBINLINE,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_OOBINLINE};
const struct optdesc opt_so_sndbuf = { "so-sndbuf", "sndbuf", OPT_SO_SNDBUF, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDBUF};
const struct optdesc opt_so_sndbuf_late={ "so-sndbuf-late","sndbuf-late",OPT_SO_SNDBUF_LATE,GROUP_SOCKET,PH_LATE,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SNDBUF };
const struct optdesc opt_so_rcvbuf = { "so-rcvbuf", "rcvbuf", OPT_SO_RCVBUF, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVBUF};
const struct optdesc opt_so_rcvbuf_late={"so-rcvbuf-late","rcvbuf-late",OPT_SO_RCVBUF_LATE,GROUP_SOCKET,PH_LATE,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_RCVBUF };
const struct optdesc opt_so_error = { "so-error", "error", OPT_SO_ERROR, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_ERROR};
const struct optdesc opt_so_type = { "so-type", "type", OPT_SO_TYPE, GROUP_SOCKET, PH_SOCKET, TYPE_INT, OFUNC_SPEC, SOL_SOCKET, SO_TYPE };
const struct optdesc opt_so_dontroute= { "so-dontroute", "dontroute", OPT_SO_DONTROUTE,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_DONTROUTE };
#ifdef SO_RCVLOWAT
const struct optdesc opt_so_rcvlowat = { "so-rcvlowat", "rcvlowat", OPT_SO_RCVLOWAT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVLOWAT };
#endif
#ifdef SO_SNDLOWAT
const struct optdesc opt_so_sndlowat = { "so-sndlowat", "sndlowat", OPT_SO_SNDLOWAT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDLOWAT };
#endif
/* end of setsockopt options of UNIX98 standard */
#ifdef SO_RCVTIMEO
const struct optdesc opt_so_rcvtimeo = { "so-rcvtimeo", "rcvtimeo", OPT_SO_RCVTIMEO, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_SOCKOPT, SOL_SOCKET, SO_RCVTIMEO };
#endif
#ifdef SO_SNDTIMEO
const struct optdesc opt_so_sndtimeo = { "so-sndtimeo", "sndtimeo", OPT_SO_SNDTIMEO, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_SOCKOPT, SOL_SOCKET, SO_SNDTIMEO };
#endif
#ifdef SO_AUDIT /* AIX 4.3.3 */
const struct optdesc opt_so_audit = { "so-audit", "audit", OPT_SO_AUDIT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_AUDIT };
#endif /* SO_AUDIT */
#ifdef SO_ATTACH_FILTER
const struct optdesc opt_so_attach_filter={"so-attach-filter","attachfilter",OPT_SO_ATTACH_FILTER,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_ATTACH_FILTER};
#endif
#ifdef SO_DETACH_FILTER
const struct optdesc opt_so_detach_filter={"so-detach-filter","detachfilter",OPT_SO_DETACH_FILTER,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DETACH_FILTER};
#endif
#ifdef SO_BINDTODEVICE /* Linux: man 7 socket */
const struct optdesc opt_so_bindtodevice={"so-bindtodevice","if",OPT_SO_BINDTODEVICE,GROUP_SOCKET,PH_PASTSOCKET,TYPE_NAME,OFUNC_SOCKOPT,SOL_SOCKET,SO_BINDTODEVICE};
#endif
#ifdef SO_BSDCOMPAT
const struct optdesc opt_so_bsdcompat= { "so-bsdcompat","bsdcompat",OPT_SO_BSDCOMPAT,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_BSDCOMPAT };
#endif
#ifdef SO_CKSUMRECV
const struct optdesc opt_so_cksumrecv= { "so-cksumrecv","cksumrecv",OPT_SO_CKSUMRECV,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_CKSUMRECV };
#endif /* SO_CKSUMRECV */
#ifdef SO_TIMESTAMP
const struct optdesc opt_so_timestamp= { "so-timestamp","timestamp",OPT_SO_TIMESTAMP,GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_TIMESTAMP };
#endif
#ifdef SO_KERNACCEPT /* AIX 4.3.3 */
const struct optdesc opt_so_kernaccept={ "so-kernaccept","kernaccept",OPT_SO_KERNACCEPT,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_KERNACCEPT};
#endif /* SO_KERNACCEPT */
#ifdef SO_NO_CHECK
const struct optdesc opt_so_no_check = { "so-no-check", "nocheck",OPT_SO_NO_CHECK, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_NO_CHECK };
#endif
#ifdef SO_NOREUSEADDR /* AIX 4.3.3 */
const struct optdesc opt_so_noreuseaddr={"so-noreuseaddr","noreuseaddr",OPT_SO_NOREUSEADDR,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET, SO_NOREUSEADDR};
#endif /* SO_NOREUSEADDR */
#ifdef SO_PASSCRED
const struct optdesc opt_so_passcred = { "so-passcred", "passcred", OPT_SO_PASSCRED, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_PASSCRED};
#endif
#ifdef SO_PEERCRED
const struct optdesc opt_so_peercred = { "so-peercred", "peercred", OPT_SO_PEERCRED, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT3,OFUNC_SOCKOPT, SOL_SOCKET, SO_PEERCRED};
#endif
#ifdef SO_PRIORITY
const struct optdesc opt_so_priority = { "so-priority", "priority", OPT_SO_PRIORITY, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_PRIORITY};
#endif
#ifdef SO_REUSEPORT /* AIX 4.3.3, BSD, HP-UX, Linux >=3.9 */
const struct optdesc opt_so_reuseport = { "so-reuseport","reuseport",OPT_SO_REUSEPORT,GROUP_SOCKET, PH_PREBIND, TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_REUSEPORT };
#endif /* defined(SO_REUSEPORT) */
#ifdef SO_SECURITY_AUTHENTICATION
const struct optdesc opt_so_security_authentication={"so-security-authentication","securityauthentication",OPT_SO_SECURITY_AUTHENTICATION,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_AUTHENTICATION};
#endif
#ifdef SO_SECURITY_ENCRYPTION_NETWORK
const struct optdesc opt_so_security_encryption_network= { "so-security-encryption-network","securityencryptionnetwork",OPT_SO_SECURITY_ENCRYPTION_NETWORK,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_ENCRYPTION_NETWORK};
#endif
#ifdef SO_SECURITY_ENCRYPTION_TRANSPORT
const struct optdesc opt_so_security_encryption_transport= { "so-security-encryption-transport","securityencryptiontransport",OPT_SO_SECURITY_ENCRYPTION_TRANSPORT,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_SECURITY_ENCRYPTION_TRANSPORT};
#endif
#ifdef SO_USE_IFBUFS
const struct optdesc opt_so_use_ifbufs= { "so-use-ifbufs","useifbufs",OPT_SO_USE_IFBUFS,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT, OFUNC_SOCKOPT, SOL_SOCKET, SO_USE_IFBUFS};
#endif /* SO_USE_IFBUFS */
#ifdef SO_USELOOPBACK /* AIX433, Solaris, HP-UX */
const struct optdesc opt_so_useloopback= { "so-useloopback","useloopback",OPT_SO_USELOOPBACK,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT, SOL_SOCKET, SO_USELOOPBACK};
#endif /* SO_USELOOPBACK */
#ifdef SO_DGRAM_ERRIND /* Solaris */
const struct optdesc opt_so_dgram_errind= { "so-dgram-errind","dgramerrind",OPT_SO_DGRAM_ERRIND,GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DGRAM_ERRIND};
#endif /* SO_DGRAM_ERRIND */
#ifdef SO_DONTLINGER /* Solaris */
const struct optdesc opt_so_dontlinger = { "so-dontlinger", "dontlinger", OPT_SO_DONTLINGER, GROUP_SOCKET,PH_PASTSOCKET,TYPE_INT,OFUNC_SOCKOPT,SOL_SOCKET,SO_DONTLINGER };
#endif
/* the SO_PROTOTYPE is OS defined on Solaris, HP-UX; we lend this for a more
general purpose */
const struct optdesc opt_so_prototype = { "so-protocol", "protocol", OPT_SO_PROTOTYPE, GROUP_SOCKET,PH_SOCKET, TYPE_INT,OFUNC_SPEC, SOL_SOCKET,SO_PROTOCOL };
#ifdef FIOSETOWN
const struct optdesc opt_fiosetown = { "fiosetown", NULL, OPT_FIOSETOWN, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_IOCTL, FIOSETOWN };
#endif
#ifdef SIOCSPGRP
const struct optdesc opt_siocspgrp = { "siocspgrp", NULL, OPT_SIOCSPGRP, GROUP_SOCKET, PH_PASTSOCKET, TYPE_INT, OFUNC_IOCTL, SIOCSPGRP };
#endif
const struct optdesc opt_bind = { "bind", NULL, OPT_BIND, GROUP_SOCKET, PH_BIND, TYPE_STRING,OFUNC_SPEC };
const struct optdesc opt_connect_timeout = { "connect-timeout", NULL, OPT_CONNECT_TIMEOUT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.connect_timeout) };
const struct optdesc opt_protocol_family = { "protocol-family", "pf", OPT_PROTOCOL_FAMILY, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC };
/* generic setsockopt() options */
const struct optdesc opt_setsockopt = { "setsockopt", "sockopt", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 };
const struct optdesc opt_setsockopt_int = { "setsockopt-int", "sockopt-int", OPT_SETSOCKOPT_INT, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_INT, OFUNC_SOCKOPT_GENERIC, 0, 0 };
const struct optdesc opt_setsockopt_bin = { "setsockopt-bin", "sockopt-bin", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 };
const struct optdesc opt_setsockopt_string = { "setsockopt-string", "sockopt-string", OPT_SETSOCKOPT_STRING, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_STRING, OFUNC_SOCKOPT_GENERIC, 0, 0 };
const struct optdesc opt_setsockopt_listen = { "setsockopt-listen", "sockopt-listen", OPT_SETSOCKOPT_LISTEN, GROUP_SOCKET,PH_PREBIND, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 };
const struct optdesc opt_null_eof = { "null-eof", NULL, OPT_NULL_EOF, GROUP_SOCKET, PH_OFFSET, TYPE_BOOL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.null_eof) };
#if WITH_GENERICSOCKET
static int xioopen_socket_connect(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
struct single *sfd = &xxfd->stream;
const char *pfname = argv[1];
const char *protname = argv[2];
const char *address = argv[3];
char *garbage;
int pf;
int proto;
int socktype = SOCK_STREAM;
int needbind = 0;
union sockaddr_union them; socklen_t themlen; size_t themsize;
union sockaddr_union us; socklen_t uslen = sizeof(us);
int result;
if (argc != 4) {
xio_syntax(argv[0], 3, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
proto = strtoul(protname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
retropt_int(opts, OPT_SO_TYPE, &socktype);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
if (applyopts_single(sfd, opts, PH_INIT) < 0)
return -1;
applyopts(sfd, -1, opts, PH_INIT);
applyopts(sfd, -1, opts, PH_EARLY);
themsize = 0;
if ((result =
dalan(address, (uint8_t *)&them.soa.sa_data, &themsize, sizeof(them), 'i'))
< 0) {
Error1("data too long: \"%s\"", address);
} else if (result > 0) {
Error1("syntax error in \"%s\"", address);
}
them.soa.sa_family = pf;
themlen = themsize +
#if HAVE_STRUCT_SOCKADDR_SALEN
sizeof(them.soa.sa_len) +
#endif
sizeof(them.soa.sa_family);
sfd->dtype = XIOREAD_STREAM|XIOWRITE_STREAM;
socket_init(0, &us);
if (retropt_bind(opts, 0 /*pf*/, socktype, proto, (struct sockaddr *)&us, &uslen, 3,
sfd->para.socket.ip.ai_flags)
!= STAT_NOACTION) {
needbind = true;
us.soa.sa_family = pf;
}
if ((result =
xioopen_connect(sfd,
needbind?&us:NULL, uslen,
(struct sockaddr *)&them, themlen,
opts, pf, socktype, proto, false)) != 0) {
return result;
}
if ((result = _xio_openlate(sfd, opts)) < 0) {
return result;
}
return STAT_OK;
}
#if WITH_LISTEN
static int xioopen_socket_listen(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
struct single *sfd = &xxfd->stream;
const char *pfname = argv[1];
const char *protname = argv[2];
const char *usname = argv[3];
char *garbage;
int pf;
int proto;
int socktype = SOCK_STREAM;
union sockaddr_union us; socklen_t uslen; size_t ussize;
struct opt *opts0;
int result;
if (argc != 4) {
xio_syntax(argv[0], 3, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
proto = strtoul(protname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
retropt_int(opts, OPT_SO_TYPE, &socktype);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
socket_init(0, &us);
ussize = 0;
if ((result =
dalan(usname, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i'))
< 0) {
Error1("data too long: \"%s\"", usname);
} else if (result > 0) {
Error1("syntax error in \"%s\"", usname);
}
uslen = ussize + sizeof(us.soa.sa_family)
#if HAVE_STRUCT_SOCKADDR_SALEN
+ sizeof(us.soa.sa_len)
#endif
;
us.soa.sa_family = pf;
if (applyopts_single(sfd, opts, PH_INIT) < 0)
return -1;
applyopts(sfd, -1, opts, PH_INIT);
applyopts(sfd, -1, opts, PH_EARLY);
opts0 = copyopts(opts, GROUP_ALL);
if ((result =
xioopen_listen(sfd, xioflags,
&us.soa, uslen,
opts, opts0, 0/*instead of pf*/, socktype, proto))
!= STAT_OK)
return result;
return STAT_OK;
}
#endif /* WITH_LISTEN */
/* we expect the form: ...:domain:type:protocol:remote-address */
static int xioopen_socket_sendto(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
int result;
if (argc != 5) {
xio_syntax(argv[0], 4, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
if ((result =
_xioopen_socket_sendto(argv[1], argv[2], argv[3], argv[4],
opts, xioflags, xxfd, addrdesc->groups))
!= STAT_OK) {
return result;
}
_xio_openlate(&xxfd->stream, opts);
return STAT_OK;
}
static
int _xioopen_socket_sendto(const char *pfname, const char *type,
const char *protname, const char *address,
struct opt *opts, int xioflags, xiofile_t *xxfd,
groups_t groups) {
xiosingle_t *sfd = &xxfd->stream;
char *garbage;
union sockaddr_union us = {{0}};
socklen_t uslen = 0; size_t ussize;
size_t themsize;
int pf;
int socktype = SOCK_RAW;
int proto;
bool needbind = false;
char *bindstring = NULL;
int result;
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
socktype = strtoul(type, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
proto = strtoul(protname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
retropt_int(opts, OPT_SO_TYPE, &socktype);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
sfd->peersa.soa.sa_family = pf;
themsize = 0;
if ((result =
dalan(address, (uint8_t *)&sfd->peersa.soa.sa_data, &themsize,
sizeof(sfd->peersa), 'i'))
< 0) {
Error1("data too long: \"%s\"", address);
} else if (result > 0) {
Error1("syntax error in \"%s\"", address);
}
sfd->salen = themsize + sizeof(sa_family_t)
#if HAVE_STRUCT_SOCKADDR_SALEN
+ sizeof(sfd->peersa.soa.sa_len)
#endif
;
#if HAVE_STRUCT_SOCKADDR_SALEN
sfd->peersa.soa.sa_len =
sizeof(sfd->peersa.soa.sa_len) + sizeof(sfd->peersa.soa.sa_family) +
themsize;
#endif
if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1;
applyopts(sfd, -1, opts, PH_INIT);
if (pf == PF_UNSPEC) {
pf = sfd->peersa.soa.sa_family;
}
sfd->dtype = XIODATA_RECVFROM;
if (retropt_string(opts, OPT_BIND, &bindstring) == 0) {
ussize = 0;
if ((result =
dalan(bindstring, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i'))
< 0) {
Error1("data too long: \"%s\"", bindstring);
} else if (result > 0) {
Error1("syntax error in \"%s\"", bindstring);
}
us.soa.sa_family = pf;
uslen = ussize + sizeof(sa_family_t)
#if HAVE_STRUCT_SOCKADDR_SALEN
+ sizeof(us.soa.sa_len)
#endif
;
needbind = true;
}
return
_xioopen_dgram_sendto(needbind?&us:NULL, uslen,
opts, xioflags, sfd, groups, pf, socktype, proto, 0);
}
/* we expect the form: ...:domain:socktype:protocol:local-address */
static
int xioopen_socket_recvfrom(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
struct single *sfd = &xxfd->stream;
const char *pfname = argv[1];
const char *typename = argv[2];
const char *protname = argv[3];
const char *address = argv[4];
char *garbage;
union sockaddr_union *us = &sfd->para.socket.la;
socklen_t uslen; size_t ussize;
int pf, socktype, proto;
char *rangename;
int result;
if (argc != 5) {
xio_syntax(argv[0], 4, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
socktype = strtoul(typename, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
proto = strtoul(protname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
retropt_int(opts, OPT_SO_TYPE, &socktype);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_NONE;
ussize = 0;
if ((result =
dalan(address, (uint8_t *)&us->soa.sa_data, &ussize, sizeof(*us), 'i'))
< 0) {
Error1("data too long: \"%s\"", address);
} else if (result > 0) {
Error1("syntax error in \"%s\"", address);
}
us->soa.sa_family = pf;
uslen = ussize + sizeof(us->soa.sa_family)
#if HAVE_STRUCT_SOCKADDR_SALEN
+ sizeof(us->soa.sa_len);
#endif
;
sfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO;
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, 0, &sfd->para.socket.range,
sfd->para.socket.ip.ai_flags)
< 0) {
free(rangename);
return STAT_NORETRY;
}
sfd->para.socket.dorange = true;
free(rangename);
}
if ((result =
_xioopen_dgram_recvfrom(sfd, xioflags, &us->soa, uslen,
opts, pf, socktype, proto, E_ERROR))
!= STAT_OK) {
return result;
}
_xio_openlate(sfd, opts);
return STAT_OK;
}
/* we expect the form: ...:domain:type:protocol:local-address */
static
int xioopen_socket_recv(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
struct single *sfd = &xxfd->stream;
const char *pfname = argv[1];
const char *typename = argv[2];
const char *protname = argv[3];
const char *address = argv[4];
char *garbage;
union sockaddr_union us;
socklen_t uslen; size_t ussize;
int pf, socktype, proto;
char *rangename;
int result;
if (argc != 5) {
xio_syntax(argv[0], 4, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
socktype = strtoul(typename, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
proto = strtoul(protname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
retropt_int(opts, OPT_SO_TYPE, &socktype);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_NONE;
ussize = 0;
if ((result =
dalan(address, (uint8_t *)&us.soa.sa_data, &ussize, sizeof(us), 'i'))
< 0) {
Error1("data too long: \"%s\"", address);
} else if (result > 0) {
Error1("syntax error in \"%s\"", address);
}
us.soa.sa_family = pf;
uslen = ussize + sizeof(sa_family_t)
#if HAVE_STRUCT_SOCKADDR_SALEN
+sizeof(us.soa.sa_len)
#endif
;
sfd->dtype = XIOREAD_RECV;
sfd->para.socket.la.soa.sa_family = pf;
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, 0, &sfd->para.socket.range,
sfd->para.socket.ip.ai_flags)
< 0) {
free(rangename);
return STAT_NORETRY;
}
sfd->para.socket.dorange = true;
free(rangename);
}
if ((result =
_xioopen_dgram_recv(sfd, xioflags, &us.soa,
uslen, opts, pf, socktype, proto, E_ERROR))
!= STAT_OK) {
return result;
}
_xio_openlate(sfd, opts);
return STAT_OK;
}
/* we expect the form: ...:domain:type:protocol:remote-address */
static int xioopen_socket_datagram(
int argc,
const char *argv[],
struct opt *opts,
int xioflags,
xiofile_t *xxfd,
const struct addrdesc *addrdesc)
{
xiosingle_t *sfd = &xxfd->stream;
const char *pfname = argv[1];
const char *typename = argv[2];
const char *protname = argv[3];
const char *address = argv[4];
char *garbage;
char *rangename;
size_t themsize;
int pf;
int result;
if (argc != 5) {
xio_syntax(argv[0], 4, argc-1, addrdesc->syntax);
return STAT_NORETRY;
}
pf = strtoul(pfname, &garbage, 0);
if (*garbage != '\0') {
Warn1("garbage in parameter: \"%s\"", garbage);
}
retropt_socket_pf(opts, &pf);
/*retropt_int(opts, OPT_IP_PROTOCOL, &proto);*/
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
sfd->peersa.soa.sa_family = pf;
themsize = 0;
if ((result =
dalan(address, (uint8_t *)&sfd->peersa.soa.sa_data, &themsize,
sizeof(sfd->peersa), 'i'))
< 0) {
Error1("data too long: \"%s\"", address);
} else if (result > 0) {
Error1("syntax error in \"%s\"", address);
}
sfd->salen = themsize + sizeof(sa_family_t);
#if HAVE_STRUCT_SOCKADDR_SALEN
sfd->peersa.soa.sa_len =
sizeof(sfd->peersa.soa.sa_len) + sizeof(sfd->peersa.soa.sa_family) +
themsize;
#endif
if ((result =
_xioopen_socket_sendto(pfname, typename, protname, address,
opts, xioflags, xxfd, addrdesc->groups))
!= STAT_OK) {
return result;
}
sfd->dtype = XIOREAD_RECV|XIOWRITE_SENDTO;
sfd->para.socket.la.soa.sa_family = sfd->peersa.soa.sa_family;
/* which reply sockets will accept - determine by range option */
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, 0, &sfd->para.socket.range,
sfd->para.socket.ip.ai_flags)
< 0) {
free(rangename);
return STAT_NORETRY;
}
sfd->para.socket.dorange = true;
sfd->dtype |= XIOREAD_RECV_CHECKRANGE;
free(rangename);
}
_xio_openlate(sfd, opts);
return STAT_OK;
}
#endif /* WITH_GENERICSOCKET */
/* EINTR not handled specially */
int xiogetpacketinfo(struct single *sfd, int fd)
{
#if defined(MSG_ERRQUEUE)
int _errno = errno;
char peername[256];
union sockaddr_union _peername;
/* union sockaddr_union _sockname; */
union sockaddr_union *pa = &_peername; /* peer address */
/* union sockaddr_union *la = &_sockname; */ /* local address */
socklen_t palen = sizeof(_peername); /* peer address size */
char ctrlbuff[1024]; /* ancillary messages */
struct msghdr msgh = {0};
msgh.msg_name = pa;
msgh.msg_namelen = palen;
#if HAVE_STRUCT_MSGHDR_MSGCONTROL
msgh.msg_control = ctrlbuff;
#endif
#if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN
msgh.msg_controllen = sizeof(ctrlbuff);
#endif
if (xiogetancillary(fd,
&msgh,
MSG_ERRQUEUE
#ifdef MSG_TRUNC
|MSG_TRUNC
#endif
) >= 0
) {
palen = msgh.msg_namelen;
Notice1("receiving packet from %s"/*"src"*/,
sockaddr_info(&pa->soa, palen, peername, sizeof(peername))/*,
sockaddr_info(&la->soa, sockname, sizeof(sockname))*/);
xiodopacketinfo(sfd, &msgh, true, true);
}
errno = _errno;
#endif /* defined(MSG_ERRQUEUE) */
return 0;
}
/* A subroutine that is common to all socket addresses that want to connect()
a socket to a peer.
Applies and consumes the following options:
PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECT,
PH_CONNECTED, PH_LATE,
OFUNC_OFFSET,
OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC
Does not fork, does not retry.
Alternate (alt) bind semantics are:
with IP sockets: lowport (selects randomly a free port from 640 to 1023)
with UNIX and abstract sockets: uses tmpname() to find a free file system
entry.
returns 0 on success.
*/
int _xioopen_connect(struct single *sfd, union sockaddr_union *us, size_t uslen,
struct sockaddr *them, size_t themlen,
struct opt *opts, int pf, int socktype, int protocol,
bool alt, int level) {
int fcntl_flags = 0;
char infobuff[256];
union sockaddr_union la;
socklen_t lalen = themlen;
int _errno;
int result;
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_EARLY);
}
#endif
if ((sfd->fd = xiosocket(opts, pf, socktype, protocol, level)) < 0) {
return STAT_RETRYLATER;
}
applyopts_offset(sfd, opts);
applyopts(sfd, -1, opts, PH_PASTSOCKET);
applyopts(sfd, -1, opts, PH_FD);
applyopts_cloexec(sfd->fd, opts);
if (xiobind(sfd, us, uslen, opts, pf, alt, level) < 0) {
return -1;
}
applyopts(sfd, -1, opts, PH_CONNECT);
if (sfd->para.socket.connect_timeout.tv_sec != 0 ||
sfd->para.socket.connect_timeout.tv_usec != 0) {
fcntl_flags = Fcntl(sfd->fd, F_GETFL);
Fcntl_l(sfd->fd, F_SETFL, fcntl_flags|O_NONBLOCK);
}
result = Connect(sfd->fd, them, themlen);
_errno = errno;
la.soa.sa_family = them->sa_family; lalen = sizeof(la);
if (Getsockname(sfd->fd, &la.soa, &lalen) < 0) {
Msg4(level-1, "getsockname(%d, %p, {%d}): %s",
sfd->fd, &la.soa, lalen, strerror(errno));
}
errno = _errno;
if (result < 0) {
if (errno == EINPROGRESS) {
if (sfd->para.socket.connect_timeout.tv_sec != 0 ||
sfd->para.socket.connect_timeout.tv_usec != 0) {
struct timeval timeout;
struct pollfd writefd;
int err;
socklen_t errlen = sizeof(err);
int result;
Info4("connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(errno));
timeout = sfd->para.socket.connect_timeout;
writefd.fd = sfd->fd;
writefd.events = (POLLOUT|POLLERR);
result = xiopoll(&writefd, 1, &timeout);
if (result < 0) {
Msg4(level, "xiopoll({%d,POLLOUT|POLLERR},,{"F_tv_sec"."F_tv_usec"): %s",
sfd->fd, timeout.tv_sec, timeout.tv_usec, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
}
if (result == 0) {
Msg2(level, "connecting to %s: %s",
sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
strerror(ETIMEDOUT));
Close(sfd->fd);
return STAT_RETRYLATER;
}
if (writefd.revents & POLLERR) {
#if 0
unsigned char dummy[1];
Read(sfd->fd, &dummy, 1); /* get error message */
Msg2(level, "connecting to %s: %s",
sockaddr_info(them, infobuff, sizeof(infobuff)),
strerror(errno));
#else
Connect(sfd->fd, them, themlen); /* get error message */
Msg4(level, "connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(errno));
#endif
Close(sfd->fd);
return STAT_RETRYLATER;
}
/* otherwise OK or network error */
result = Getsockopt(sfd->fd, SOL_SOCKET, SO_ERROR, &err, &errlen);
if (result != 0) {
Msg2(level, "getsockopt(%d, SOL_SOCKET, SO_ERROR, ...): %s",
sfd->fd, strerror(err));
Close(sfd->fd);
return STAT_RETRYLATER;
}
Debug2("getsockopt(%d, SOL_SOCKET, SO_ERROR, { %d }) -> 0",
sfd->fd, err);
if (err != 0) {
Msg4(level, "connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(err));
Close(sfd->fd);
return STAT_RETRYLATER;
}
Fcntl_l(sfd->fd, F_SETFL, fcntl_flags);
} else {
Warn4("connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(errno));
}
} else if (pf == PF_UNIX) {
/* this is for UNIX domain sockets: a connect attempt seems to be
the only way to distinguish stream and datagram sockets.
And no ancillary message expected
*/
int _errno = errno;
Info4("connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(errno));
/* caller must handle this condition */
Close(sfd->fd); sfd->fd = -1;
errno = _errno;
return STAT_RETRYLATER;
} else {
/* try to find details about error, especially from ICMP */
xiogetpacketinfo(sfd, sfd->fd);
/* continue mainstream */
Msg4(level, "connect(%d, %s, "F_Zd"): %s",
sfd->fd, sockaddr_info(them, themlen, infobuff, sizeof(infobuff)),
themlen, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
}
} else { /* result >= 0 */
Notice1("successfully connected from local address %s",
sockaddr_info(&la.soa, themlen, infobuff, sizeof(infobuff)));
}
applyopts_fchown(sfd->fd, opts); /* OPT_USER, OPT_GROUP */
applyopts(sfd, -1, opts, PH_CONNECTED);
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_LATE);
}
#endif
applyopts(sfd, -1, opts, PH_LATE);
return STAT_OK;
}
/* a subroutine that is common to all socket addresses that want to connect
to a peer address.
might fork.
applies and consumes the following option:
PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECT,
PH_CONNECTED, PH_LATE,
OFUNC_OFFSET,
OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC
returns 0 on success.
*/
int xioopen_connect(struct single *sfd, union sockaddr_union *us, size_t uslen,
struct sockaddr *them, size_t themlen,
struct opt *opts, int pf, int socktype, int protocol,
bool alt) {
bool dofork = false;
struct opt *opts0;
char infobuff[256];
int level;
int result;
retropt_bool(opts, OPT_FORK, &dofork);
opts0 = copyopts(opts, GROUP_ALL);
Notice1("opening connection to %s",
sockaddr_info(them, themlen, infobuff, sizeof(infobuff)));
do { /* loop over retries and forks */
#if WITH_RETRY
if (sfd->forever || sfd->retry) {
level = E_INFO;
} else
#endif /* WITH_RETRY */
level = E_ERROR;
result =
_xioopen_connect(sfd, us, uslen, them, themlen, opts,
pf, socktype, protocol, alt, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
if (sfd->forever || sfd->retry) {
--sfd->retry;
if (result == STAT_RETRYLATER) {
Nanosleep(&sfd->intervall, NULL);
}
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
continue;
}
return STAT_NORETRY;
#endif /* WITH_RETRY */
default:
return result;
}
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
#if WITH_RETRY
if (dofork) {
pid_t pid;
int level = E_ERROR;
if (sfd->forever || sfd->retry) {
level = E_WARN; /* most users won't expect a problem here,
so Notice is too weak */
}
while ((pid = xio_fork(false, level, sfd->shutup)) < 0) {
--sfd->retry;
if (sfd->forever || sfd->retry) {
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
Nanosleep(&sfd->intervall, NULL); continue;
}
return STAT_RETRYLATER;
}
if (pid == 0) { /* child process */
break;
}
/* parent process */
Close(sfd->fd);
/* with and without retry */
Nanosleep(&sfd->intervall, NULL);
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
continue; /* with next socket() bind() connect() */
} else
#endif /* WITH_RETRY */
{
break;
}
#if 0
if ((result = _xio_openlate(fd, opts)) < 0)
return result;
#endif
} while (true);
return 0;
}
/* common to xioopen_udp_sendto, ..unix_sendto, ..rawip
applies and consumes the following option:
PH_PASTSOCKET, PH_FD, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_CONNECTED, PH_LATE
OFUNC_OFFSET
OPT_SO_TYPE, OPT_SO_PROTOTYPE, OPT_USER, OPT_GROUP, OPT_CLOEXEC
*/
int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
union sockaddr_union *us, socklen_t uslen,
struct opt *opts,
int xioflags, xiosingle_t *sfd, groups_t groups,
int pf, int socktype, int ipproto, bool alt) {
int level = E_ERROR;
union sockaddr_union la; socklen_t lalen = sizeof(la);
char infobuff[256];
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_EARLY);
}
#endif
if ((sfd->fd = xiosocket(opts, pf, socktype, ipproto, level)) < 0) {
return STAT_RETRYLATER;
}
applyopts_offset(sfd, opts);
applyopts_single(sfd, opts, PH_PASTSOCKET);
applyopts(sfd, -1, opts, PH_PASTSOCKET);
applyopts_single(sfd, opts, PH_FD);
applyopts(sfd, -1, opts, PH_FD);
applyopts_cloexec(sfd->fd, opts);
if (xiobind(sfd, us, uslen, opts, pf, alt, level) < 0) {
return -1;
}
/*applyopts(sfd, -1, opts, PH_CONNECT);*/
if (Getsockname(sfd->fd, &la.soa, &lalen) < 0) {
Warn4("getsockname(%d, %p, {%d}): %s",
sfd->fd, &la.soa, lalen, strerror(errno));
}
applyopts_fchown(sfd->fd, opts);
applyopts(sfd, -1, opts, PH_CONNECTED);
#if WITH_UNIX
if (pf == PF_UNIX && us != NULL) {
applyopts_named(us->un.sun_path, opts, PH_LATE);
}
#endif
/*0 applyopts(sfd, -1, opts, PH_LATE); */
/* sfd->dtype = DATA_RECVFROM; *//* no, the caller must set this (ev _SKIPIP) */
Notice1("successfully prepared local socket %s",
sockaddr_info(&la.soa, lalen, infobuff, sizeof(infobuff)));
return STAT_OK;
}
/* when the recvfrom address (with option fork) receives a packet it keeps this
packet in the IP stacks input queue and forks a sub process. The sub process
then reads this packet for processing its data.
There is a problem because the parent process would find the same packet
again if it calls select()/poll() before the child process has read the
packet.
To solve this problem we implement the following mechanism:
Before forking an unnamed pipe (fifo) is created. The sub process closes the
write side when it has read the packet. The parent process waits until the
read side of the pipe gives EOF and only then continues to listen.
*/
/* waits for incoming packet, checks its source address and port. Depending
on fork option, it may fork a subprocess.
Returns STAT_OK if a packet was accepted; with fork option, this is already in
a new subprocess!
Other return values indicate a problem; this can happen in the master
process or in a subprocess.
This function does not retry. If you need retries, handle this is a
loop in the calling function.
after fork, we set the forever/retry of the child process to 0
applies and consumes the following options:
PH_INIT, PH_PREBIND, PH_BIND, PH_PASTBIND, PH_EARLY, PH_PREOPEN, PH_FD,
PH_CONNECTED, PH_LATE, PH_LATE2
OPT_FORK, OPT_SO_TYPE, OPT_SO_PROTOTYPE, cloexec, OPT_RANGE, tcpwrap
EINTR is not handled specially.
*/
int _xioopen_dgram_recvfrom(struct single *sfd, int xioflags,
struct sockaddr *us, socklen_t uslen,
struct opt *opts,
int pf, int socktype, int proto, int level) {
char *rangename;
bool dofork = false;
pid_t pid; /* mostly int; only used with fork */
char infobuff[256];
char lisname[256];
bool drop = false; /* true if current packet must be dropped */
int result;
retropt_bool(opts, OPT_FORK, &dofork);
if (dofork) {
if (!(xioflags & XIO_MAYFORK)) {
Error("option fork not allowed here");
return STAT_NORETRY;
}
sfd->flags |= XIO_DOESFORK;
}
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
if ((sfd->fd = xiosocket(opts, pf, socktype, proto, level)) < 0) {
return STAT_RETRYLATER;
}
applyopts(sfd, -1, opts, PH_PASTSOCKET);
/*! applyopts(sfd, -1, opts, PH_FD); */
applyopts_cloexec(sfd->fd, opts);
if (xiobind(sfd, (union sockaddr_union *)us, uslen,
opts, pf, 0, level) < 0) {
return -1;
}
applyopts(sfd, -1, opts, PH_PASTBIND);
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN);
}
#endif /* WITH_UNIX */
#if WITH_IP4 /*|| WITH_IP6*/
switch (proto) {
case IPPROTO_UDP:
#ifdef IPPROTO_UDPLITE
case IPPROTO_UDPLITE:
#endif
if (pf == PF_INET && ((struct sockaddr_in *)us)->sin_port == 0 ||
pf == PF_INET6 && ((struct sockaddr_in6 *)us)->sin6_port == 0) {
struct sockaddr_storage bound;
socklen_t bndlen = sizeof(bound);
char sockbuff[256];
Getsockname(sfd->fd, (struct sockaddr *)&bound, &bndlen);
sockaddr_info((struct sockaddr *)&bound, sizeof(struct sockaddr_storage), sockbuff, sizeof(sockbuff));
Notice1("_xioopen_dgram_recvfrom(): bound to %s", sockbuff);
}
}
#endif
/* for generic sockets, this has already been retrieved */
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, pf, &sfd->para.socket.range,
sfd->para.socket.ip.ai_flags)
< 0) {
free(rangename);
return STAT_NORETRY;
}
free(rangename);
sfd->para.socket.dorange = true;
}
#if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP
xio_retropt_tcpwrap(sfd, opts);
#endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */
if (xioparms.logopt == 'm') {
Info("starting recvfrom loop, switching to syslog");
diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y';
} else {
Info("starting recvfrom loop");
}
if (dofork) {
xiosetchilddied();
}
while (true) { /* but we only loop if fork option is set */
char peername[256];
union sockaddr_union _peername;
union sockaddr_union _sockname;
union sockaddr_union *pa = &_peername; /* peer address */
union sockaddr_union *la = &_sockname; /* local address */
socklen_t palen = sizeof(_peername); /* peer address size */
char ctrlbuff[1024]; /* ancillary messages */
struct msghdr msgh = {0};
int trigger[2]; /* for socketpair that indicates consumption of packet */
int rc;
socket_init(pf, pa);
if (drop) {
char *dummy[2];
Recv(sfd->fd, dummy, sizeof(dummy), 0);
drop = true;
}
Info("Recvfrom: Checking/waiting for next packet");
/* loop until select()/poll() returns valid */
do {
struct pollfd readfd;
/*? int level = E_ERROR;*/
if (us != NULL) {
Notice1("receiving on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname)));
} else {
Notice1("receiving IP protocol %u", proto);
}
readfd.fd = sfd->fd;
readfd.events = POLLIN;
if (xiopoll(&readfd, 1, NULL) > 0) {
break;
}
if (errno == EINTR) {
continue;
}
Msg2(level, "poll({%d,,},,-1): %s", sfd->fd, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
} while (true);
msgh.msg_name = pa;
msgh.msg_namelen = palen;
#if HAVE_STRUCT_MSGHDR_MSGCONTROL
msgh.msg_control = ctrlbuff;
#endif
#if HAVE_STRUCT_MSGHDR_MSGCONTROLLEN
msgh.msg_controllen = sizeof(ctrlbuff);
#endif
while ((rc = xiogetancillary(sfd->fd,
&msgh,
MSG_PEEK
#ifdef MSG_TRUNC
|MSG_TRUNC
#endif
)) < 0 &&
errno == EINTR) ;
if (rc < 0) return STAT_RETRYLATER;
palen = msgh.msg_namelen;
Notice1("receiving packet from %s"/*"src"*/,
sockaddr_info(&pa->soa, palen, peername, sizeof(peername))/*,
sockaddr_info(&la->soa, sockname, sizeof(sockname))*/);
xiodopacketinfo(sfd, &msgh, true, true);
if (xiocheckpeer(sfd, pa, la) < 0) {
/* drop packet */
char buff[512];
Recv(sfd->fd, buff, sizeof(buff), 0);
continue;
}
Info1("permitting packet from %s",
sockaddr_info(&pa->soa, palen,
infobuff, sizeof(infobuff)));
/* set the env vars describing the local and remote sockets */
/*xiosetsockaddrenv("SOCK", la, lalen, proto);*/
xiosetsockaddrenv("PEER", pa, palen, proto);
applyopts(sfd, -1, opts, PH_FD);
applyopts(sfd, -1, opts, PH_CONNECTED);
sfd->peersa = *(union sockaddr_union *)pa;
sfd->salen = palen;
if (dofork) {
Info("Generating socketpair that triggers parent when packet has been consumed");
if (Socketpair(PF_UNIX, SOCK_STREAM, 0, trigger) < 0) {
Error1("socketpair(PF_UNIX, SOCK_STREAM, 0, ...): %s", strerror(errno));
}
if ((pid = xio_fork(false, level, sfd->shutup)) < 0) {
Close(trigger[0]);
Close(trigger[1]);
Close(sfd->fd);
return STAT_RETRYLATER;
}
if (pid == 0) { /* child */
Close(trigger[0]);
sfd->triggerfd = trigger[1];
Fcntl_l(sfd->triggerfd, F_SETFD, FD_CLOEXEC);
#if WITH_RETRY
/* !? */
sfd->retry = 0;
sfd->forever = 0;
level = E_ERROR;
#endif /* WITH_RETRY */
#if WITH_UNIX
/* with UNIX sockets: only listening parent is allowed to remove
the socket file */
sfd->opt_unlink_close = false;
#endif /* WITH_UNIX */
break;
}
/* Parent */
Close(trigger[1]);
{
char buf[1];
while (Read(trigger[0], buf, 1) < 0 && errno == EINTR) ;
}
Close(trigger[0]);
Info("continue listening");
} else {
break;
}
}
if ((result = _xio_openlate(sfd, opts)) != 0)
return STAT_NORETRY;
return STAT_OK;
}
/* returns STAT_* */
int _xioopen_dgram_recv(struct single *sfd, int xioflags,
struct sockaddr *us, socklen_t uslen,
struct opt *opts, int pf, int socktype, int proto,
int level) {
char *rangename;
if (applyopts_single(sfd, opts, PH_INIT) < 0) return STAT_NORETRY;
if ((sfd->fd = xiosocket(opts, pf, socktype, proto, level)) < 0) {
return STAT_RETRYLATER;
}
applyopts(sfd, -1, opts, PH_PASTSOCKET);
/*! applyopts(sfd, -1, opts, PH_FD); */
applyopts_cloexec(sfd->fd, opts);
if (xiobind(sfd, (union sockaddr_union *)us, uslen, opts, pf, 0, level) < 0) {
return -1;
}
#if WITH_UNIX
if (pf == AF_UNIX && us != NULL) {
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_EARLY);
applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_PREOPEN);
}
#endif /* WITH_UNIX */
#if WITH_IP4 /*|| WITH_IP6*/
switch (proto) {
case IPPROTO_UDP:
#ifdef IPPROTO_UDPLITE
case IPPROTO_UDPLITE:
#endif
if (pf == PF_INET && ((struct sockaddr_in *)us)->sin_port == 0 ||
pf == PF_INET6 && ((struct sockaddr_in6 *)us)->sin6_port == 0) {
struct sockaddr_storage bound;
socklen_t bndlen = sizeof(bound);
char sockbuff[256];
Getsockname(sfd->fd, (struct sockaddr *)&bound, &bndlen);
sockaddr_info((struct sockaddr *)&bound, sizeof(struct sockaddr_storage), sockbuff, sizeof(sockbuff));
Notice1("_xioopen_dgram_recv(): bound to %s", sockbuff);
}
}
#endif
if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) {
if (xioparserange(rangename, pf, &sfd->para.socket.range,
sfd->para.socket.ip.ai_flags)
< 0) {
free(rangename);
return STAT_NORETRY;
}
free(rangename);
sfd->para.socket.dorange = true;
}
#if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP
xio_retropt_tcpwrap(sfd, opts);
#endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */
if (xioparms.logopt == 'm') {
Info("starting recv loop, switching to syslog");
diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y';
} else {
Info("starting recv loop");
}
return STAT_OK;
}
int retropt_socket_pf(struct opt *opts, int *pf) {
char *pfname;
if (retropt_string(opts, OPT_PROTOCOL_FAMILY, &pfname) >= 0) {
if (isdigit((unsigned char)pfname[0])) {
*pf = strtoul(pfname, NULL /*!*/, 0);
#if WITH_IP4
} else if (!strcasecmp("inet", pfname) ||
!strcasecmp("inet4", pfname) ||
!strcasecmp("ip4", pfname) ||
!strcasecmp("ipv4", pfname) ||
!strcasecmp("2", pfname)) {
*pf = PF_INET;
#endif /* WITH_IP4 */
#if WITH_IP6
} else if (!strcasecmp("inet6", pfname) ||
!strcasecmp("ip6", pfname) ||
!strcasecmp("ipv6", pfname) ||
!strcasecmp("10", pfname)) {
*pf = PF_INET6;
#endif /* WITH_IP6 */
} else {
Error1("unknown protocol family \"%s\"", pfname);
/*! Warn("falling back to INET");*/
}
free(pfname);
return 0;
}
return -1;
}
/* This function calls recvmsg(..., MSG_PEEK, ...) to obtain information about
the arriving packet, thus it does not "consume" the packet.
In msgh the msg_name pointer must refer to an (empty) sockaddr storage.
Returns STAT_OK on success, or STAT_RETRYLATER when an error occurred,
including EINTR.
(recvmsg() retrieves just meta info, not the data)
*/
int xiogetancillary(int fd, struct msghdr *msgh, int flags) {
char peekbuff[1];
#if HAVE_STRUCT_IOVEC
struct iovec iovec;
#endif
#if HAVE_STRUCT_IOVEC
iovec.iov_base = peekbuff;
iovec.iov_len = sizeof(peekbuff);
msgh->msg_iov = &iovec;
msgh->msg_iovlen = 1;
#endif
#if HAVE_STRUCT_MSGHDR_MSGFLAGS
msgh->msg_flags = 0;
#endif
if (Recvmsg(fd, msgh, flags) < 0) {
Info1("recvmsg(): %s", strerror(errno));
return STAT_RETRYLATER;
}
return STAT_OK;
}
/* works through the ancillary messages found in the given socket header record
and logs the relevant information (E_DEBUG, E_INFO).
calls protocol/layer specific functions for handling the messages
creates appropriate environment vars if withenv is set */
int xiodopacketinfo(
struct single *sfd,
struct msghdr *msgh,
bool withlog,
bool withenv)
{
#if defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA)
struct cmsghdr *cmsg;
/* parse ancillary messages */
cmsg = CMSG_FIRSTHDR(msgh);
while (cmsg != NULL) {
int num = 0; /* number of data components of a ancill.msg */
int i;
char typbuff[16], *typp;
char nambuff[128], *namp;
char valbuff[256], *valp;
char envbuff[256], *envp;
Info3("ancillary message in xiodopacketinfo(): len="F_Zu", level=%d, type=%d",
cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type);
if (withlog) {
xiodump(CMSG_DATA(cmsg),
cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg),
valbuff, sizeof(valbuff)-1, 0);
Debug4("ancillary message: len="F_cmsg_len", level=%d, type=%d, data=%s",
cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type,
valbuff);
}
/* try to get the anc.msg. contents in handy components, protocol/level
dependent */
switch (cmsg->cmsg_level) {
case SOL_SOCKET:
xiolog_ancillary_socket(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1,
nambuff, sizeof(nambuff)-1,
envbuff, sizeof(envbuff)-1,
valbuff, sizeof(valbuff)-1);
break;
#if WITH_IP4 || WITH_IP6
case SOL_IP:
xiolog_ancillary_ip(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1,
nambuff, sizeof(nambuff)-1,
envbuff, sizeof(envbuff)-1,
valbuff, sizeof(valbuff)-1);
break;
#endif /* WITH_IP4 || WITH_IP6 */
#if WITH_IP6
case SOL_IPV6:
xiolog_ancillary_ip6(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1,
nambuff, sizeof(nambuff)-1,
envbuff, sizeof(envbuff)-1,
valbuff, sizeof(valbuff)-1);
break;
#endif /* WITH_IP6 */
#if _WITH_INTERFACE && HAVE_STRUCT_CMSGHDR && HAVE_STRUCT_TPACKET_AUXDATA
case SOL_PACKET:
xiolog_ancillary_packet(sfd, cmsg, &num, typbuff, sizeof(typbuff)-1,
nambuff, sizeof(nambuff)-1,
envbuff, sizeof(envbuff)-1,
valbuff, sizeof(valbuff)-1);
break;
#endif /* HAVE_STRUCT_CMSGHDR && HAVE_STRUCT_TPACKET_AUXDATA */
default:
num = 1;
snprintf(typbuff, sizeof(typbuff)-1, "LEVEL%u", cmsg->cmsg_level);
snprintf(nambuff, sizeof(nambuff)-1, "type%u", cmsg->cmsg_type);
xiodump(CMSG_DATA(cmsg),
cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg),
valbuff, sizeof(valbuff)-1, 0);
}
/* here the info is in typbuff (one string), nambuff (num consecutive
strings), and valbuff (num consecutive strings) */
i = 0;
typp = typbuff; namp = nambuff; envp = envbuff; valp = valbuff;
while (i < num) {
if (withlog) {
Info3("ancillary message: %s: %s=%s", typp, namp, valp);
}
if (withenv) {
if (*envp) {
xiosetenv(envp, valp, 1, NULL);
} else if (!strcasecmp(typp+strlen(typp)-strlen(namp), namp)) {
xiosetenv(typp, valp, 1, NULL);
} else {
xiosetenv2(typp, namp, valp, 1, NULL);
}
}
if (++i == num) break;
namp = strchr(namp, '\0')+1;
envp = strchr(envp, '\0')+1;
valp = strchr(valp, '\0')+1;
}
cmsg = CMSG_NXTHDR(msgh, cmsg);
}
return 0;
#else /* !(defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA)) */
return -1;
#endif /* !(defined(HAVE_STRUCT_CMSGHDR) && defined(CMSG_DATA)) */
}
/* check if peer address is within permitted range.
return >= 0 if so. */
int xiocheckrange(union sockaddr_union *sa, struct xiorange *range) {
switch (sa->soa.sa_family) {
#if WITH_IP4
case PF_INET:
return
xiocheckrange_ip4(&sa->ip4, range);
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6:
return
xiocheckrange_ip6(&sa->ip6, range);
#endif /* WITH_IP6 */
#if 0
case PF_UNSPEC:
{
socklen_t i;
for (i = 0; i < sizeof(sa->soa.sa_data); ++i) {
if ((range->netmask.soa.sa_data[i] & sa->soa.sa_data[i]) != range->netaddr.soa.sa_data[i]) {
return -1;
}
}
return 0;
}
#endif
}
return -1;
}
int xiocheckpeer(xiosingle_t *sfd,
union sockaddr_union *pa, union sockaddr_union *la) {
char infobuff[256];
int result;
#if WITH_IP4
if (sfd->para.socket.dorange) {
if (pa == NULL) { return -1; }
if (xiocheckrange(pa, &sfd->para.socket.range) < 0) {
char infobuff[256];
Warn1("refusing connection from %s due to range option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
}
Info1("permitting connection from %s due to range option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
}
#endif /* WITH_IP4 */
#if WITH_TCP || WITH_UDP
if (sfd->para.socket.ip.dosourceport) {
if (pa == NULL) { return -1; }
#if WITH_IP4
if (pa->soa.sa_family == AF_INET &&
ntohs(((struct sockaddr_in *)pa)->sin_port) != sfd->para.socket.ip.sourceport) {
Warn1("refusing connection from %s due to wrong sourceport",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
}
#endif /* WITH_IP4 */
#if WITH_IP6
if (pa->soa.sa_family == AF_INET6 &&
ntohs(((struct sockaddr_in6 *)pa)->sin6_port) != sfd->para.socket.ip.sourceport) {
Warn1("refusing connection from %s due to wrong sourceport",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
}
#endif /* WITH_IP6 */
Info1("permitting connection from %s due to sourceport option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
} else if (sfd->para.socket.ip.lowport) {
if (pa == NULL) { return -1; }
if (pa->soa.sa_family == AF_INET &&
ntohs(((struct sockaddr_in *)pa)->sin_port) >= IPPORT_RESERVED) {
Warn1("refusing connection from %s due to lowport option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
}
#if WITH_IP6
else if (pa->soa.sa_family == AF_INET6 &&
ntohs(((struct sockaddr_in6 *)pa)->sin6_port) >=
IPPORT_RESERVED) {
Warn1("refusing connection from %s due to lowport option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
}
#endif /* WITH_IP6 */
Info1("permitting connection from %s due to lowport option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
}
#endif /* WITH_TCP || WITH_UDP */
#if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP
result = xio_tcpwrap_check(sfd, la, pa);
if (result < 0) {
char infobuff[256];
Warn1("refusing connection from %s due to tcpwrapper option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
return -1;
} else if (result > 0) {
Info1("permitting connection from %s due to tcpwrapper option",
sockaddr_info(&pa->soa, 0,
infobuff, sizeof(infobuff)));
}
#endif /* (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */
return 0; /* permitted */
}
#if HAVE_STRUCT_CMSGHDR
/* converts the ancillary message in *cmsg into a form useable for further
processing. knows the specifics of common message types.
returns the number of resulting syntax elements in *num
returns a sequence of \0 terminated type strings in *typbuff
returns a sequence of \0 terminated name strings in *nambuff
returns a sequence of \0 terminated value strings in *valbuff
the respective len parameters specify the available space in the buffers
returns STAT_OK or other STAT_*
*/
static int
xiolog_ancillary_socket(
struct single *sfd,
struct cmsghdr *cmsg,
int *num,
char *typbuff, int typlen,
char *nambuff, int namlen,
char *envbuff, int envlen,
char *valbuff, int vallen)
{
const char *cmsgtype, *cmsgname, *cmsgenvn;
size_t msglen;
struct timeval *tv;
int rc = STAT_OK;
#if defined(CMSG_DATA)
msglen = cmsg->cmsg_len-((char *)CMSG_DATA(cmsg)-(char *)cmsg);
switch (cmsg->cmsg_type) {
#ifdef SO_PASSCRED
case SO_PASSCRED: /* this is really a UNIX/LOCAL message */
/*! needs implementation */
#endif /* SO_PASSCRED */
#ifdef SO_RIGHTS
case SO_RIGHTS: /* this is really a UNIX/LOCAL message */
/*! needs implementation */
#endif
default: /* binary data */
snprintf(typbuff, typlen, "SOCKET.%u", cmsg->cmsg_type);
nambuff[0] = '\0'; strncat(nambuff, "data", namlen-1);
xiodump(CMSG_DATA(cmsg), msglen, valbuff, vallen, 0);
return STAT_OK;
#ifdef SO_TIMESTAMP
# ifdef SCM_TIMESTAMP
case SCM_TIMESTAMP:
# else
case SO_TIMESTAMP:
# endif
tv = (struct timeval *)CMSG_DATA(cmsg);
cmsgtype =
#ifdef SCM_TIMESTAMP
"SCM_TIMESTAMP" /* FreeBSD */
#else
"SO_TIMESTAMP" /* Linux */
#endif
;
cmsgname = "timestamp";
cmsgenvn = "TIMESTAMP";
{ time_t t = tv->tv_sec; ctime_r(&t, valbuff); }
snprintf(strchr(valbuff, '\0')-1/*del \n*/, vallen-strlen(valbuff)+1, ", %06ld usecs", (long)tv->tv_usec);
break;
#endif /* defined(SO_TIMESTAMP) */
;
}
/* when we come here we provide a single parameter
with type in cmsgtype, name in cmsgname,
and value already in valbuff */
*num = 1;
if (strlen(cmsgtype) >= typlen) rc = STAT_WARNING;
typbuff[0] = '\0'; strncat(typbuff, cmsgtype, typlen-1);
if (strlen(cmsgname) >= namlen) rc = STAT_WARNING;
nambuff[0] = '\0'; strncat(nambuff, cmsgname, namlen-1);
if (strlen(cmsgenvn) >= envlen) rc = STAT_WARNING;
envbuff[0] = '\0'; strncat(envbuff, cmsgenvn, envlen-1);
return rc;
#else /* !defined(CMSG_DATA) */
return STAT_NORETRY;
#endif /* !defined(CMSG_DATA) */
}
#endif /* HAVE_STRUCT_CMSGHDR */
/* return the name of the interface with given index
or NULL if is fails
The system call requires an arbitrary socket; the calling program may
provide one in parameter ins to avoid creation of a dummy socket. ins must
be <0 if it does not specify a socket fd. */
char *xiogetifname(int ind, char *val, int ins) {
#if !HAVE_PROTOTYPE_LIB_if_indextoname
int s;
struct ifreq ifr;
if (ins >= 0) {
s = ins;
} else {
if ((s = Socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) {
Error1("socket(PF_INET, SOCK_DGRAM, IPPROTO_IP): %s", strerror(errno));
return NULL;
}
}
#if HAVE_STRUCT_IFREQ_IFR_INDEX
ifr.ifr_index = ind;
#elif HAVE_STRUCT_IFREQ_IFR_IFINDEX
ifr.ifr_ifindex = ind;
#endif
#ifdef SIOCGIFNAME
if(Ioctl(s, SIOCGIFNAME, &ifr) < 0) {
#if HAVE_STRUCT_IFREQ_IFR_INDEX
Info3("ioctl(%d, SIOCGIFNAME, {..., ifr_index=%d, ...}: %s",
s, ifr.ifr_index, strerror(errno));
#elif HAVE_STRUCT_IFREQ_IFR_IFINDEX
Info3("ioctl(%d, SIOCGIFNAME, {..., ifr_ifindex=%d, ...}: %s",
s, ifr.ifr_ifindex, strerror(errno));
#endif
if (ins < 0) Close(s);
return NULL;
}
#endif /* SIOCGIFNAME */
if (ins < 0) Close(s);
strcpy(val, ifr.ifr_name);
return val;
#else /* HAVE_PROTOTYPE_LIB_if_indextoname */
return if_indextoname(ind, val);
#endif /* HAVE_PROTOTYPE_LIB_if_indextoname */
}
/* parses a network specification consisting of an address and a mask. */
int xioparsenetwork(
const char *rangename,
int pf,
struct xiorange *range,
const int ai_flags[2])
{
size_t addrlen = 0, masklen = 0;
int result;
switch (pf) {
#if WITH_IP4
case PF_INET:
return xioparsenetwork_ip4(rangename, range, ai_flags);
break;
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6:
return xioparsenetwork_ip6(rangename, range, ai_flags);
break;
#endif /* WITH_IP6 */
case PF_UNSPEC:
{
char *addrname;
const char *maskname;
if ((maskname = strchr(rangename, ':')) == NULL) {
Error1("syntax error in range \"%s\" of unspecified address family: use <addr>:<mask>", rangename);
return STAT_NORETRY;
}
++maskname; /* skip ':' */
if ((addrname = Malloc(maskname-rangename)) == NULL) {
return STAT_NORETRY;
}
strncpy(addrname, rangename, maskname-rangename-1); /* ok */
addrname[maskname-rangename-1] = '\0';
result =
dalan(addrname, (uint8_t *)&range->netaddr.soa.sa_data, &addrlen,
sizeof(range->netaddr)-(size_t)(&((struct sockaddr *)0)->sa_data)
/* data length */, 'i');
if (result < 0) {
Error1("data too long: \"%s\"", addrname);
free(addrname); return STAT_NORETRY;
} else if (result > 0) {
Error1("syntax error in \"%s\"", addrname);
free(addrname); return STAT_NORETRY;
}
free(addrname);
result =
dalan(maskname, (uint8_t *)&range->netmask.soa.sa_data, &masklen,
sizeof(range->netaddr)-(size_t)(&((struct sockaddr *)0)->sa_data)
/* data length */, 'i');
if (result < 0) {
Error1("data too long: \"%s\"", maskname);
return STAT_NORETRY;
} else if (result > 0) {
Error1("syntax error in \"%s\"", maskname);
return STAT_NORETRY;
}
if (addrlen != masklen) {
Error2("network address is "F_Zu" bytes long, mask is "F_Zu" bytes long",
addrlen, masklen);
/* recover by padding the shorter component with 0 */
memset((char *)&range->netaddr.soa.sa_data+addrlen, 0,
MAX(0, addrlen-masklen));
memset((char *)&range->netmask.soa.sa_data+masklen, 0,
MAX(0, masklen-addrlen));
}
}
break;
default:
Error1("range option not supported with address family %d", pf);
return STAT_NORETRY;
}
return STAT_OK;
}
/* parses a string of form address/bits or address:mask, and fills the fields
of the range union. The addr component is masked with mask. */
int xioparserange(
const char *rangename,
int pf,
struct xiorange *range,
const int ai_flags[2])
{
int i;
if (xioparsenetwork(rangename, pf, range, ai_flags) < 0) {
Error2("failed to parse or resolve range \"%s\" (pf=%d)", rangename, pf);
return -1;
}
/* we have parsed the address and mask; now we make sure that the stored
address has 0 where mask is 0, to simplify comparisions */
switch (pf) {
#if WITH_IP4
case PF_INET:
range->netaddr.ip4.sin_addr.s_addr &= range->netmask.ip4.sin_addr.s_addr;
break;
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6:
return xiorange_ip6andmask(range);
break;
#endif /* WITH_IP6 */
case PF_UNSPEC:
for (i = 0; i < sizeof(range->netaddr); ++i) {
((char *)&range->netaddr)[i] &= ((char *)&range->netmask)[i];
}
break;
default:
Error1("range option not supported with address family %d", pf);
return STAT_NORETRY;
}
return 0;
}
/* set environment variables describing (part of) a socket address, e.g.
SOCAT_SOCKADDR. lr (local/remote) specifies a string like "SOCK" or "PEER".
proto should correspond to the third parameter of socket(2) and is used to
determine the presence of port information. */
int xiosetsockaddrenv(const char *lr,
union sockaddr_union *sau, socklen_t salen,
int proto) {
# define XIOSOCKADDRENVLEN 256
char namebuff[XIOSOCKADDRENVLEN];
char valuebuff[XIOSOCKADDRENVLEN];
int idx = 0, result;
strcpy(namebuff, lr);
switch (sau->soa.sa_family) {
#if WITH_UNIX
case PF_UNIX:
result =
xiosetsockaddrenv_unix(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr),
valuebuff, XIOSOCKADDRENVLEN,
&sau->un, salen, proto);
xiosetenv(namebuff, valuebuff, 1, NULL);
break;
#endif /* WITH_UNIX */
#if WITH_IP4
case PF_INET:
do {
result =
xiosetsockaddrenv_ip4(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr),
valuebuff, XIOSOCKADDRENVLEN,
&sau->ip4, proto);
xiosetenv(namebuff, valuebuff, 1, NULL);
namebuff[strlen(lr)] = '\0'; ++idx;
} while (result > 0);
break;
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6:
strcpy(namebuff, lr);
do {
result =
xiosetsockaddrenv_ip6(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr),
valuebuff, XIOSOCKADDRENVLEN,
&sau->ip6, proto);
xiosetenv(namebuff, valuebuff, 1, NULL);
namebuff[strlen(lr)] = '\0'; ++idx;
} while (result > 0);
break;
#endif /* WITH_IP6 */
#if WITH_VSOCK
case PF_VSOCK:
strcpy(namebuff, lr);
do {
result =
xiosetsockaddrenv_vsock(idx, strchr(namebuff, '\0'), XIOSOCKADDRENVLEN-strlen(lr),
valuebuff, XIOSOCKADDRENVLEN,
&sau->vm, proto);
xiosetenv(namebuff, valuebuff, 1, NULL);
namebuff[strlen(lr)] = '\0'; ++idx;
} while (result > 0);
break;
#endif /* WITH_VSOCK */
#if LATER
case PF_PACKET:
result = xiosetsockaddrenv_packet(lr, (void *)sau, proto); break;
#endif
default:
result = -1;
break;
}
return result;
# undef XIOSOCKADDRENVLEN
}
#endif /* _WITH_SOCKET */
/* these do sockets internally */
/* retrieves options so-type and so-prototype from opts, calls socket, and
ev. generates an appropriate error message.
returns 0 on success or -1 if an error occurred. */
int
xiosocket(struct opt *opts, int pf, int socktype, int proto, int msglevel) {
int result;
retropt_int(opts, OPT_SO_TYPE, &socktype);
retropt_int(opts, OPT_SO_PROTOTYPE, &proto);
applyopts(NULL, -1, opts, PH_PRESOCKET);
result = Socket(pf, socktype, proto);
if (result < 0) {
int _errno = errno;
Msg4(msglevel, "socket(%d, %d, %d): %s",
pf, socktype, proto, strerror(errno));
errno = _errno;
return -1;
}
return result;
}
/* retrieves options so-type and so-prototype from opts, calls socketpair, and
ev. generates an appropriate error message.
returns 0 on success or -1 if an error occurred. */
int
xiosocketpair(struct opt *opts, int pf, int socktype, int proto, int sv[2]) {
int result;
retropt_int(opts, OPT_SO_TYPE, &socktype);
retropt_int(opts, OPT_SO_PROTOTYPE, &proto);
result = Socketpair(pf, socktype, proto, sv);
if (result < 0) {
Error5("socketpair(%d, %d, %d, %p): %s",
pf, socktype, proto, sv, strerror(errno));
return -1;
}
return result;
}
/* Binds a socket to a socket address. Handles IP (internet protocol), UNIX
domain, Linux abstract UNIX domain.
The bind address us may be NULL in which case no bind() happens, except with
alt (on option unix-bind-tempname (bind-tempname)).
Alternate (atl) bind semantics are:
with IP sockets: lowport (selects randomly a free port from 640 to 1023)
with UNIX and abstract sockets: uses a method similar to tmpname() to
find a free file system entry.
*/
int xiobind(
struct single *sfd,
union sockaddr_union *us,
size_t uslen,
struct opt *opts,
int pf,
bool alt,
int level)
{
char infobuff[256];
int result;
if (false /* for canonical reasons */) {
;
#if WITH_UNIX
} else if (pf == PF_UNIX) {
if (alt && us != NULL) {
bool abstract = false;
char *usrname = NULL, *sockname;
#if WITH_ABSTRACT_UNIXSOCKET
abstract = (us->un.sun_path[0] == '\0');
#endif
if (uslen == ((char *)&us->un.sun_path-(char *)us)) {
usrname = NULL;
} else {
#if WITH_ABSTRACT_UNIXSOCKET
if (abstract)
usrname = strndup(us->un.sun_path+1, sizeof(us->un.sun_path)-1);
else
#endif
usrname = strndup(us->un.sun_path, sizeof(us->un.sun_path));
if (usrname == NULL) {
int _errno = errno;
Error2("strndup(\"%s\", "F_Zu"): out of memory",
us->un.sun_path, sizeof(us->un.sun_path));
errno = _errno;
return -1;
}
}
do { /* loop over tempnam bind() attempts */
sockname = xio_tempnam(usrname, abstract);
if (sockname == NULL) {
Error2("tempnam(\"%s\"): %s", usrname, strerror(errno));
free(usrname);
return -1;
}
strncpy(us->un.sun_path+(abstract?1:0), sockname, sizeof(us->un.sun_path));
uslen = sizeof(&((struct sockaddr_un *)0)->sun_path) +
Min(strlen(sockname), sizeof(us->un.sun_path)); /*?*/
free(sockname);
if (Bind(sfd->fd, (struct sockaddr *)us, uslen) < 0) {
Msg4(errno==EADDRINUSE?E_INFO:level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info((struct sockaddr *)us, uslen,
infobuff, sizeof(infobuff)),
uslen, strerror(errno));
if (errno != EADDRINUSE) {
free(usrname);
Close(sfd->fd);
return STAT_RETRYLATER;
}
} else {
break; /* could bind to path, good, continue past loop */
}
} while (true);
free(usrname);
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
} else
if (us != NULL) {
if (Bind(sfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
}
applyopts_named(us->un.sun_path, opts, PH_PREOPEN);
}
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
applyopts(sfd, sfd->fd, opts, PH_BIND);
#endif /* WITH_UNIX */
#if WITH_TCP || WITH_UDP
} else if (alt) {
union sockaddr_union sin, *sinp;
unsigned short *port, i, N;
div_t dv;
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
applyopts(sfd, sfd->fd, opts, PH_BIND);
/* prepare sockaddr for bind probing */
if (us) {
sinp = us;
} else {
if (pf == AF_INET) {
socket_in_init(&sin.ip4);
#if WITH_IP6
} else {
socket_in6_init(&sin.ip6);
#endif
}
sinp = &sin;
}
if (pf == AF_INET) {
port = &sin.ip4.sin_port;
#if WITH_IP6
} else if (pf == AF_INET6) {
port = &sin.ip6.sin6_port;
#endif
} else {
port = 0; /* just to make compiler happy */
}
/* combine random+step variant to quickly find a free port when only
few are in use, and certainly find a free port in defined time even
if there are almost all in use */
/* dirt 1: having tcp/udp code in socket function */
/* dirt 2: using a time related system call for init of random */
{
/* generate a random port, with millisecond random init */
#if 0
struct timeb tb;
ftime(&tb);
srandom(tb.time*1000+tb.millitm);
#else
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
if ((result = Gettimeofday(&tv, &tz)) < 0) {
Warn2("gettimeofday(%p, {0,0}): %s", &tv, strerror(errno));
}
srandom(tv.tv_sec*1000000+tv.tv_usec);
#endif
}
/* Note: IPPORT_RESERVED is from includes, 1024 */
dv = div(random(), IPPORT_RESERVED-XIO_IPPORT_LOWER);
i = N = XIO_IPPORT_LOWER + dv.rem;
do { /* loop over lowport bind() attempts */
*port = htons(i);
if (Bind(sfd->fd, &sinp->soa, sizeof(*sinp)) < 0) {
Msg4(errno==EADDRINUSE?E_INFO:level,
"bind(%d, {%s}, "F_Zd"): %s", sfd->fd,
sockaddr_info(&sinp->soa, sizeof(*sinp), infobuff, sizeof(infobuff)),
sizeof(*sinp), strerror(errno));
if (errno != EADDRINUSE) {
Close(sfd->fd);
return STAT_RETRYLATER;
}
} else {
break; /* could bind to port, good, continue past loop */
}
--i; if (i < XIO_IPPORT_LOWER) i = IPPORT_RESERVED-1;
if (i == N) {
Msg(level, "no low port available");
/*errno = EADDRINUSE; still assigned */
Close(sfd->fd);
return STAT_RETRYLATER;
}
} while (i != N);
#endif /* WITH_TCP || WITH_UDP */
} else {
applyopts(sfd, sfd->fd, opts, PH_PREBIND);
if (us) {
applyopts(sfd, sfd->fd, opts, PH_BIND);
if (Bind(sfd->fd, &us->soa, uslen) < 0) {
Msg4(level, "bind(%d, {%s}, "F_Zd"): %s",
sfd->fd, sockaddr_info(&us->soa, uslen, infobuff, sizeof(infobuff)),
uslen, strerror(errno));
Close(sfd->fd);
return STAT_RETRYLATER;
}
}
}
applyopts(sfd, -1, opts, PH_PASTBIND);
return 0;
}
/* Handles the SO_REUSEADDR socket option for TCP LISTEN addresses depending on
Socat option so-reuseaddr:
Option not applied: set it to 1
Option applied with a value: set it to the value
Option applied eith empty value "so-reuseaddr=": do not call setsockopt() for
SO_REUSEADDR
Return 0 on success, or -1 with errno when an error occurred.
*/
int xiosock_reuseaddr(int fd, int ipproto, struct opt *opts)
{
union integral val;
union integral notnull;
int _errno;
val.u_int = 0;
notnull.u_bool = false;
if (ipproto == IPPROTO_TCP) {
val.u_int = 1;
notnull.u_bool = true;
}
retropt_2integrals(opts, OPT_SO_REUSEADDR, &val, &notnull);
if (notnull.u_bool) {
if (Setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val.u_int, sizeof(int))
!= 0) {
_errno = errno;
Error4("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, { %d }, "F_Zu"): %s",
fd, val.u_int, sizeof(val.u_int), strerror(errno));
errno = _errno;
return -1;
}
}
return 0;
}