1
0
Fork 0
mirror of https://repo.or.cz/socat.git synced 2025-07-19 09:22:57 +00:00

Reworked IPAPP clients

This commit is contained in:
Gerhard 2025-02-10 12:48:04 +01:00
parent 63f67101f4
commit 7b26406d96
16 changed files with 1660 additions and 649 deletions

View file

@ -19,6 +19,7 @@ const struct optdesc opt_sourceport = { "sourceport", "sp", OPT_SOURCEPORT
/*const struct optdesc opt_port = { "port", NULL, OPT_PORT, GROUP_IPAPP, PH_BIND, TYPE_USHORT, OFUNC_SPEC };*/
const struct optdesc opt_lowport = { "lowport", NULL, OPT_LOWPORT, GROUP_IPAPP, PH_LATE, TYPE_BOOL, OFUNC_SPEC };
#if _WITH_IP4 || _WITH_IP6
/* we expect the form "host:port" */
int xioopen_ipapp_connect(
@ -31,20 +32,18 @@ int xioopen_ipapp_connect(
{
struct single *sfd = &xxfd->stream;
struct opt *opts0 = NULL;
const char *hostname = argv[1], *portname = argv[2];
int pf = addrdesc->arg3;
int socktype = addrdesc->arg1;
int ipproto = addrdesc->arg2;
int pf = addrdesc->arg3;
const char *hostname = argv[1], *portname = argv[2];
bool dofork = false;
int maxchildren = 0;
union sockaddr_union us_sa, *us = &us_sa;
socklen_t uslen = sizeof(us_sa);
struct addrinfo **themarr, *themp;
char infobuff[256];
struct addrinfo **bindarr = NULL;
struct addrinfo **themarr = NULL;
uint16_t bindport = 0;
bool needbind = false;
bool lowport = false;
int level;
int i;
int level = E_ERROR;
int result;
if (argc != 3) {
@ -52,97 +51,84 @@ int xioopen_ipapp_connect(
return STAT_NORETRY;
}
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
/* Apply and retrieve some options */
result = _xioopen_ipapp_init(sfd, xioflags, opts,
&dofork, &maxchildren,
&pf, &socktype, &ipproto);
if (result != STAT_OK)
return result;
if (applyopts_single(sfd, opts, PH_INIT) < 0)
return -1;
applyopts(sfd, -1, opts, PH_INIT);
opts0 = opts; /* save remaining options for each loop */
opts = NULL;
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;
}
Notice2("opening connection to %s:%s", hostname, portname);
retropt_int(opts, OPT_MAX_CHILDREN, &maxchildren);
if (! dofork && maxchildren) {
Error("option max-children not allowed without option fork");
return STAT_NORETRY;
}
if (_xioopen_ipapp_prepare(opts, &opts0, hostname, portname, &pf, ipproto,
sfd->para.socket.ip.ai_flags,
&themarr, us, &uslen, &needbind, &lowport,
socktype) != STAT_OK) {
return STAT_NORETRY;
}
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
if (xioparms.logopt == 'm') {
Info("starting connect loop, switching to syslog");
diag_set('y', xioparms.syslogfac); xioparms.logopt = 'y';
} else {
Info("starting connect loop");
}
do { /* loop over retries, and forks */
/* Loop over themarr (which had been "ai_sorted") */
result = STAT_RETRYLATER;
i = 0;
themp = themarr[i++];
while (themp != NULL) {
Notice1("opening connection to %s",
sockaddr_info(themp->ai_addr, themp->ai_addrlen,
infobuff, sizeof(infobuff)));
do { /* loop over retries and/or forks */
int _errno;
#if WITH_RETRY
if (sfd->forever || sfd->retry) {
level = E_INFO;
} else if (themarr[i] != NULL) {
level = E_WARN;
} else
if (sfd->forever || sfd->retry) {
level = E_NOTICE;
} else
#endif /* WITH_RETRY */
level = E_ERROR;
level = E_WARN;
result =
_xioopen_connect(sfd,
needbind?us:NULL, uslen,
themp->ai_addr, themp->ai_addrlen,
opts, pf?pf:themp->ai_family, socktype, ipproto,
lowport, level);
if (result == STAT_OK)
break;
themp = themarr[i++];
if (themp == NULL) {
result = STAT_RETRYLATER;
}
}
opts = copyopts(opts0, GROUP_ALL);
result =
_xioopen_ipapp_prepare(&opts, opts0, hostname, portname,
pf, socktype, ipproto,
sfd->para.socket.ip.ai_flags,
&themarr, &bindarr, &bindport, &needbind, &lowport);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (sfd->forever || sfd->retry) {
--sfd->retry;
if (result == STAT_RETRYLATER) {
if (sfd->forever || sfd->retry--) {
if (result == STAT_RETRYLATER)
Nanosleep(&sfd->intervall, NULL);
}
dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL);
if (bindarr != NULL) xiofreeaddrinfo(bindarr);
xiofreeaddrinfo(themarr);
freeopts(opts);
continue;
}
#endif /* WITH_RETRY */
default:
/* FALLTHROUGH */
case STAT_NORETRY:
if (bindarr != NULL) xiofreeaddrinfo(bindarr);
xiofreeaddrinfo(themarr);
free(opts0);free(opts);
freeopts(opts);
freeopts(opts0);
return result;
}
result =
_xioopen_ipapp_connect(sfd, hostname, opts, themarr,
needbind, bindarr, bindport, lowport, level);
_errno = errno;
if (bindarr != NULL)
xiofreeaddrinfo(bindarr);
xiofreeaddrinfo(themarr);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (sfd->forever || sfd->retry--) {
if (result == STAT_RETRYLATER) {
Nanosleep(&sfd->intervall, NULL);
}
freeopts(opts);
continue;
}
#endif /* WITH_RETRY */
/* FALLTHROUGH */
default:
Error4("%s:%s:%s: %s", argv[0], argv[1], argv[2],
_errno?strerror(_errno):"(See above)");
freeopts(opts);
freeopts(opts0);
return result;
}
@ -155,16 +141,18 @@ int xioopen_ipapp_connect(
so Notice is too weak */
}
while ((pid = xio_fork(false, level, sfd->shutup)) < 0) {
if (sfd->forever || --sfd->retry) {
Nanosleep(&sfd->intervall, NULL); continue;
if (sfd->forever || sfd->retry--) {
Nanosleep(&sfd->intervall, NULL);
continue;
}
xiofreeaddrinfo(themarr);
free(opts0);
freeopts(opts);
freeopts(opts0);
return STAT_RETRYLATER;
}
if (pid == 0) { /* child process */
sfd->forever = false; sfd->retry = 0;
sfd->forever = false;
sfd->retry = 0;
break;
}
@ -176,109 +164,266 @@ int xioopen_ipapp_connect(
Info1("all %d allowed children are active, waiting", maxchildren);
Nanosleep(&sfd->intervall, NULL);
}
dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL);
freeopts(opts);
continue; /* with next socket() bind() connect() */
} else
#endif /* WITH_RETRY */
{
break;
}
} while (true);
} while (true); /* end of loop over retries and/or forks */
/* only "active" process breaks (master without fork, or child) */
xiofreeaddrinfo(themarr);
if ((result = _xio_openlate(sfd, opts)) < 0) {
free(opts0);free(opts);
return result;
}
free(opts0); free(opts);
return 0;
Notice2("successfully connected to %s:%s", hostname, portname);
result = _xio_openlate(sfd, opts);
freeopts(opts);
freeopts(opts0);
return result;
}
/* returns STAT_OK on success or some other value on failure
/* This function performs static initializations for addresses like TCP-CONNECT
before start of the outer loop:
it retrieves some options
returns STAT_OK on success or some other value on failure;
applies and consumes the following options:
PH_INIT, OPT_FORK, OPT_MAX_CHILDREN, OPT_PROTOCOL_FAMILY, OPT_SO_TYPE,
OPT_SO_PROTOTYPE
*/
int _xioopen_ipapp_init(
struct single *sfd,
int xioflags,
struct opt *opts,
bool *dofork,
int *maxchildren,
int *pf,
int *socktype,
int *ipproto)
{
if (sfd->howtoend == END_UNSPEC)
sfd->howtoend = END_SHUTDOWN;
if (applyopts_single(sfd, opts, PH_INIT) < 0)
return -1;
if (applyopts(sfd, -1, opts, PH_INIT) < 0)
return -1;
retropt_bool(opts, OPT_FORK, dofork);
if (dofork) {
if (!(xioflags & XIO_MAYFORK)) {
Error1("%s: option fork not allowed here", sfd->addr->defname);
return STAT_NORETRY;
}
sfd->flags |= XIO_DOESFORK;
}
retropt_int(opts, OPT_MAX_CHILDREN, maxchildren);
if (! dofork && maxchildren) {
Error1("%s: option max-children not allowed without option fork", sfd->addr->defname);
return STAT_NORETRY;
}
retropt_socket_pf(opts, pf);
retropt_int(opts, OPT_SO_TYPE, socktype);
retropt_int(opts, OPT_SO_PROTOTYPE, ipproto);
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
if (xioparms.logopt == 'm') {
Info("starting connect loop, switching to syslog");
diag_set('y', xioparms.syslogfac);
xioparms.logopt = 'y';
} else {
Info("starting connect loop");
}
return STAT_OK;
}
/* This function performs preparations for addresses like TCP-CONNECT
at the beginning of the outer (retry/fork) loop:
it evaluates some options and performs name resolution of both server
(target, "them") address and bind ("us") address.
It is intended to be invoked before the connect loop starts;
returns STAT_OK on success or some other value on failure;
applies and consumes the following options:
PH_EARLY
OPT_PROTOCOL_FAMILY, OPT_BIND, OPT_SOURCEPORT, OPT_LOWPORT
*/
int
_xioopen_ipapp_prepare(
struct opt *opts,
struct opt **opts0,
const char *hostname,
const char *portname,
int *pf,
int protocol,
const int ai_flags[2],
struct addrinfo ***themarr,
union sockaddr_union *us,
socklen_t *uslen,
bool *needbind,
bool *lowport,
int socktype) {
OPT_BIND, OPT_SOURCEPORT, OPT_LOWPORT
returns STAT_OK, STAT_RETRYLATER, or STAT_NORETRY (+errno)
*/
int _xioopen_ipapp_prepare(
struct opt **opts,
struct opt *opts0,
const char *hostname,
const char *portname,
int pf,
int socktype,
int protocol,
const int ai_flags[2],
struct addrinfo ***themarr, /* always from getaddrinfo(); xiofreeaddrinfo()! */
struct addrinfo ***bindarr, /* on bind from getaddrinfo(); xiofreeaddrinfo()! */
uint16_t *bindport, /* for bind without address */
bool *needbind,
bool *lowport)
{
uint16_t port;
int rc;
retropt_socket_pf(opts, pf);
*opts = copyopts(opts0, GROUP_ALL);
if (hostname != NULL || portname != NULL) {
rc = xiogetaddrinfo(hostname, portname, *pf, socktype, protocol,
rc = xiogetaddrinfo(hostname, portname, pf, socktype, protocol,
themarr, ai_flags);
if (rc == EAI_AGAIN) {
Warn4("_xioopen_ipapp_prepare(node=\"%s\", service=\"%s\", pf=%d, ...): %s",
hostname?hostname:"NULL", portname?portname:"NULL",
*pf, gai_strerror(rc));
pf, gai_strerror(rc));
errno = EAGAIN;
return STAT_RETRYLATER;
} else if (rc != 0) {
Error4("_xioopen_ipapp_prepare(node=\"%s\", service=\"%s\", pf=%d, ...): %s",
hostname?hostname:"NULL", portname?portname:"NULL",
*pf, (rc == EAI_SYSTEM)?strerror(errno):gai_strerror(rc));
pf, (rc == EAI_SYSTEM)?strerror(errno):gai_strerror(rc));
errno = 0; /* unspecified */
return STAT_NORETRY; /*! STAT_RETRYLATER? */
}
}
applyopts(NULL, -1, opts, PH_EARLY);
applyopts(NULL, -1, *opts, PH_EARLY);
/* 3 means: IP address AND port accepted */
if (retropt_bind(opts, (*pf!=PF_UNSPEC)?*pf:(**themarr)->ai_family,
socktype, protocol, (struct sockaddr *)us, uslen, 3,
ai_flags)
if (retropt_bind_ip(*opts, pf, socktype, protocol, bindarr, 3, ai_flags)
!= STAT_NOACTION) {
*needbind = true;
} else {
switch ((*pf!=PF_UNSPEC)?*pf:(**themarr)->ai_family) {
#if WITH_IP4
case PF_INET: socket_in_init(&us->ip4); *uslen = sizeof(us->ip4); break;
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6: socket_in6_init(&us->ip6); *uslen = sizeof(us->ip6); break;
#endif /* WITH_IP6 */
default: Error("unsupported protocol family");
}
}
if (retropt_2bytes(opts, OPT_SOURCEPORT, &port) >= 0) {
switch ((*pf!=PF_UNSPEC)?*pf:(**themarr)->ai_family) {
if (retropt_2bytes(*opts, OPT_SOURCEPORT, &port) >= 0) {
if (*bindarr) {
struct addrinfo **bindp;
bindp = *bindarr;
switch ((*bindp)->ai_family) {
#if WITH_IP4
case PF_INET: us->ip4.sin_port = htons(port); break;
case PF_INET: ((struct sockaddr_in *)(*bindp)->ai_addr)->sin_port = htons(port); break;
#endif /* WITH_IP4 */
#if WITH_IP6
case PF_INET6: us->ip6.sin6_port = htons(port); break;
case PF_INET6: ((struct sockaddr_in6 *)(*bindp)->ai_addr)->sin6_port = htons(port); break;
#endif /* WITH_IP6 */
default: Error("unsupported protocol family");
default:
Error("unsupported protocol family");
errno = EPROTONOSUPPORT;
return STAT_NORETRY;
}
} else {
*bindport = port;
}
*needbind = true;
}
retropt_bool(opts, OPT_LOWPORT, lowport);
*opts0 = copyopts(opts, GROUP_ALL);
retropt_bool(*opts, OPT_LOWPORT, lowport);
return STAT_OK;
}
#endif /* _WITH_IP4 || _WITH_IP6 */
/* Tries to connect to the addresses in themarr, for each one it tries to bind
to the addresses in bindarr.
Ends on success or when all attempts failed.
Returns STAT_OK on success, or STAT_RETRYLATER (+errno) on failure. */
int _xioopen_ipapp_connect(struct single *sfd,
const char *hostname,
struct opt *opts,
struct addrinfo **themarr,
bool needbind,
struct addrinfo **bindarr,
uint16_t bindport,
bool lowport,
int level)
{
struct addrinfo **themp;
struct addrinfo **bindp;
union sockaddr_union bindaddr = {0};
union sockaddr_union *bindaddrp = NULL;
socklen_t bindlen = 0;
char infobuff[256];
int _errno;
int result = STAT_OK;
--level;
/* Loop over server addresses (themarr) */
themp = themarr;
while (*themp != NULL) {
Notice1("opening connection to %s",
sockaddr_info((*themp)->ai_addr, (*themp)->ai_addrlen,
infobuff, sizeof(infobuff)));
if (*(themp+1) == NULL) {
++level; /* last attempt */
}
/* Loop over array (list) of bind addresses */
if (needbind && bindarr != NULL) {
/* Bind by hostname, use resolvers results list */
bindp = bindarr;
while (*bindp != NULL) {
if ((*bindp)->ai_family == (*themp)->ai_family)
break;
++bindp;
}
if (*bindp == NULL) {
Warn3("%s: No bind address with matching address family (%d) of %s available",
sfd->addr->defname, (*themp)->ai_family, hostname);
++themp;
if ((*themp) == NULL) {
result = STAT_RETRYLATER;
}
_errno = ENOPROTOOPT;
continue;
}
bindaddrp = (union sockaddr_union *)(*bindp)->ai_addr;
bindlen = (*bindp)->ai_addrlen;
} else if (needbind && bindport) {
/* Bind by sourceport option */
switch ((*themp)->ai_family) {
case PF_INET:
bindaddr.ip4.sin_family = (*themp)->ai_family;
bindaddr.ip4.sin_port = htons(bindport);
bindaddrp = &bindaddr;
bindlen = sizeof(bindaddr.ip4);
break;
case PF_INET6:
bindaddr.ip6.sin6_family = (*themp)->ai_family;
bindaddr.ip6.sin6_port = htons(bindport);
bindaddrp = &bindaddr;
bindlen = sizeof(bindaddr.ip6);
break;
}
}
result =
_xioopen_connect(sfd,
bindaddrp, bindlen,
(*themp)->ai_addr, (*themp)->ai_addrlen,
opts, /*pf?pf:*/(*themp)->ai_family, (*themp)->ai_socktype, (*themp)->ai_protocol,
lowport, level);
if (result == STAT_OK)
break;
_errno = errno;
++themp;
if (*themp == NULL)
result = STAT_RETRYLATER;
} /* end of loop over target addresses */
if (result != STAT_OK)
errno = _errno;
return result;
}
#if WITH_TCP && WITH_LISTEN
/*
applies and consumes the following options: