/* source: xio-ipapp.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 TCP and UDP related options */ #include "xiosysincludes.h" #if WITH_TCP || WITH_UDP || WITH_SCTP || WITH_DCCP || WITH_UDPLITE #include "xioopen.h" #include "xio-socket.h" #include "xio-ip.h" #include "xio-listen.h" #include "xio-ip6.h" #include "xio-ipapp.h" const struct optdesc opt_sourceport = { "sourceport", "sp", OPT_SOURCEPORT, GROUP_IPAPP, PH_LATE,TYPE_2BYTE, OFUNC_SPEC }; /*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( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xxfd, const struct addrdesc *addrdesc) { 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; bool dofork = false; int maxchildren = 0; struct addrinfo **bindarr = NULL; struct addrinfo **themarr = NULL; uint16_t bindport = 0; bool needbind = false; bool lowport = false; int level = E_ERROR; int result; if (argc != 3) { xio_syntax(argv[0], 2, argc-1, addrdesc->syntax); return STAT_NORETRY; } /* Apply and retrieve some options */ result = _xioopen_ipapp_init(sfd, xioflags, opts, &dofork, &maxchildren, &pf, &socktype, &ipproto); if (result != STAT_OK) return result; opts0 = opts; /* save remaining options for each loop */ opts = NULL; Notice2("opening connection to %s:%s", hostname, portname); do { /* loop over retries and/or forks */ int _errno; #if WITH_RETRY if (sfd->forever || sfd->retry) { level = E_NOTICE; } else #endif /* WITH_RETRY */ level = E_WARN; 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--) { if (result == STAT_RETRYLATER) Nanosleep(&sfd->intervall, NULL); if (bindarr != NULL) xiofreeaddrinfo(bindarr); xiofreeaddrinfo(themarr); freeopts(opts); continue; } #endif /* WITH_RETRY */ /* FALLTHROUGH */ case STAT_NORETRY: if (bindarr != NULL) xiofreeaddrinfo(bindarr); xiofreeaddrinfo(themarr); 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; } #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) { if (sfd->forever || sfd->retry--) { Nanosleep(&sfd->intervall, NULL); continue; } freeopts(opts); freeopts(opts0); return STAT_RETRYLATER; } if (pid == 0) { /* child process */ sfd->forever = false; sfd->retry = 0; break; } /* parent process */ Close(sfd->fd); /* with and without retry */ Nanosleep(&sfd->intervall, NULL); while (maxchildren > 0 && num_child >= maxchildren) { Info1("all %d allowed children are active, waiting", maxchildren); Nanosleep(&sfd->intervall, NULL); } freeopts(opts); continue; /* with next socket() bind() connect() */ } else #endif /* WITH_RETRY */ { break; } } while (true); /* end of loop over retries and/or forks */ /* only "active" process breaks (master without fork, or child) */ Notice2("successfully connected to %s:%s", hostname, portname); result = _xio_openlate(sfd, opts); freeopts(opts); freeopts(opts0); return result; } /* 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_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; *opts = copyopts(opts0, GROUP_ALL); if (hostname != NULL || portname != NULL) { 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)); 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)); errno = 0; /* unspecified */ return STAT_NORETRY; /*! STAT_RETRYLATER? */ } } applyopts(NULL, -1, *opts, PH_EARLY); /* 3 means: IP address AND port accepted */ if (retropt_bind_ip(*opts, pf, socktype, protocol, bindarr, 3, ai_flags) != STAT_NOACTION) { *needbind = true; } 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: ((struct sockaddr_in *)(*bindp)->ai_addr)->sin_port = htons(port); break; #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: ((struct sockaddr_in6 *)(*bindp)->ai_addr)->sin6_port = htons(port); break; #endif /* WITH_IP6 */ default: Error("unsupported protocol family"); errno = EPROTONOSUPPORT; return STAT_NORETRY; } } else { *bindport = port; } *needbind = true; } 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) { #if WITH_IP4 case PF_INET: bindaddr.ip4.sin_family = (*themp)->ai_family; bindaddr.ip4.sin_port = htons(bindport); bindaddrp = &bindaddr; bindlen = sizeof(bindaddr.ip4); break; #endif #if WITH_IP6 case PF_INET6: bindaddr.ip6.sin6_family = (*themp)->ai_family; bindaddr.ip6.sin6_port = htons(bindport); bindaddrp = &bindaddr; bindlen = sizeof(bindaddr.ip6); break; #endif } } 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: OPT_PROTOCOL_FAMILY, OPT_BIND */ int _xioopen_ipapp_listen_prepare( struct opt *opts, struct opt **opts0, const char *portname, int *pf, int ipproto, const int ai_flags[2], union sockaddr_union *us, socklen_t *uslen, int socktype) { char *bindname = NULL; int ai_flags2[2]; int result; retropt_socket_pf(opts, pf); retropt_string(opts, OPT_BIND, &bindname); /* Set AI_PASSIVE, except when it is explicitly disabled */ ai_flags2[0] = ai_flags[0]; ai_flags2[1] = ai_flags[1]; if (!(ai_flags2[1] & AI_PASSIVE)) ai_flags2[0] |= AI_PASSIVE; result = xioresolve(bindname, portname, *pf, socktype, ipproto, us, uslen, ai_flags2); if (result != STAT_OK) { /*! STAT_RETRY? */ return result; } *opts0 = copyopts(opts, GROUP_ALL); return STAT_OK; } /* we expect the form: port */ /* currently only used for TCP4 */ int xioopen_ipapp_listen( int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, const struct addrdesc *addrdesc) { struct single *sfd = &xfd->stream; struct opt *opts0 = NULL; int socktype = addrdesc->arg1; int ipproto = addrdesc->arg2; int pf = addrdesc->arg3; union sockaddr_union us_sa, *us = &us_sa; socklen_t uslen = sizeof(us_sa); int result; if (argc != 2) { xio_syntax(argv[0], 2, argc-1, addrdesc->syntax); return STAT_NORETRY; } xioinit_ip(&pf, xioparms.default_ip); if (pf == PF_UNSPEC) { #if WITH_IP4 && WITH_IP6 switch (xioparms.default_ip) { case '4': pf = PF_INET; break; case '6': pf = PF_INET6; break; default: break; /* includes \0 */ } #elif WITH_IP6 pf = PF_INET6; #else pf = PF_INET; #endif } 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); if (_xioopen_ipapp_listen_prepare(opts, &opts0, argv[1], &pf, ipproto, sfd->para.socket.ip.ai_flags, us, &uslen, socktype) != STAT_OK) { return STAT_NORETRY; } if ((result = xioopen_listen(sfd, xioflags, (struct sockaddr *)us, uslen, opts, opts0, pf, socktype, ipproto)) != 0) return result; return 0; } #endif /* WITH_TCP && WITH_LISTEN */ #endif /* WITH_TCP || WITH_UDP || WITH_SCTP || WITH_DCCP || WITH_UDPLITE */