/* 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 #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 /* 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; int socktype = addrdesc->arg1; int ipproto = addrdesc->arg2; int pf = addrdesc->arg3; const char *hostname = argv[1], *portname = argv[2]; bool dofork = false; union sockaddr_union us_sa, *us = &us_sa; socklen_t uslen = sizeof(us_sa); struct addrinfo *themlist, *themp; char infobuff[256]; bool needbind = false; bool lowport = false; int level; struct addrinfo **ai_sorted; int i; int result; if (argc != 3) { xio_syntax(argv[0], 2, argc-1, addrdesc->syntax); return STAT_NORETRY; } xioinit_ip(&pf, xioparms.default_ip); sfd->howtoend = END_SHUTDOWN; if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; applyopts(sfd, -1, opts, PH_INIT); retropt_bool(opts, OPT_FORK, &dofork); if (_xioopen_ipapp_prepare(opts, &opts0, hostname, portname, &pf, ipproto, sfd->para.socket.ip.ai_flags, &themlist, 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"); } /* Count addrinfo entries */ themp = themlist; i = 0; while (themp != NULL) { ++i; themp = themp->ai_next; } ai_sorted = Calloc((i+1), sizeof(struct addrinfo *)); if (ai_sorted == NULL) return STAT_RETRYLATER; /* Generate a list of addresses sorted by preferred ip version */ _xio_sort_ip_addresses(themlist, ai_sorted); do { /* loop over retries, and forks */ /* Loop over themlist - no, over ai_sorted */ result = STAT_RETRYLATER; i = 0; themp = ai_sorted[i++]; while (themp != NULL) { Notice1("opening connection to %s", sockaddr_info(themp->ai_addr, themp->ai_addrlen, infobuff, sizeof(infobuff))); #if WITH_RETRY if (sfd->forever || sfd->retry || ai_sorted[i] != NULL) { level = E_INFO; } else #endif /* WITH_RETRY */ level = E_ERROR; 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 = ai_sorted[i++]; if (themp == NULL) { result = STAT_RETRYLATER; } } 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) { Nanosleep(&sfd->intervall, NULL); } dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL); continue; } #endif /* WITH_RETRY */ default: free(ai_sorted); free(opts0);free(opts); 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; } free(ai_sorted); free(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); dropopts(opts, PH_ALL); free(opts); opts = copyopts(opts0, GROUP_ALL); continue; /* with next socket() bind() connect() */ } else #endif /* WITH_RETRY */ { break; } } while (true); /* only "active" process breaks (master without fork, or child) */ free(ai_sorted); xiofreeaddrinfo(themlist); if ((result = _xio_openlate(sfd, opts)) < 0) { free(opts0);free(opts); return result; } free(opts0); free(opts); return 0; } /* 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 **themlist, union sockaddr_union *us, socklen_t *uslen, bool *needbind, bool *lowport, int socktype) { uint16_t port; int result; retropt_socket_pf(opts, pf); if (hostname != NULL || portname != NULL) { if ((result = xiogetaddrinfo(hostname, portname, *pf, socktype, protocol, themlist, ai_flags)) != STAT_OK) { return STAT_NORETRY; /*! STAT_RETRYLATER? */ } } applyopts(NULL, -1, opts, PH_EARLY); /* 3 means: IP address AND port accepted */ if (retropt_bind(opts, (*pf!=PF_UNSPEC)?*pf:(*themlist)->ai_family, socktype, protocol, (struct sockaddr *)us, uslen, 3, ai_flags) != STAT_NOACTION) { *needbind = true; } else { switch ((*pf!=PF_UNSPEC)?*pf:(*themlist)->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:(*themlist)->ai_family) { #if WITH_IP4 case PF_INET: us->ip4.sin_port = htons(port); break; #endif /* WITH_IP4 */ #if WITH_IP6 case PF_INET6: us->ip6.sin6_port = htons(port); break; #endif /* WITH_IP6 */ default: Error("unsupported protocol family"); } *needbind = true; } retropt_bool(opts, OPT_LOWPORT, lowport); *opts0 = copyopts(opts, GROUP_ALL); return STAT_OK; } #endif /* WITH_IP4 */ #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 explicitely 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 } 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_IP4 && WITH_TCP && WITH_LISTEN */ /* Sort the records of an addrinfo list themp (as returned by getaddrinfo), return the sorted list in the array ai_sorted (takes at most n entries including the terminating NULL) Returns 0 on success. */ int _xio_sort_ip_addresses( struct addrinfo *themlist, struct addrinfo **ai_sorted) { struct addrinfo *themp; int i; int ipv[3]; int ipi = 0; /* Make a simple array of IP version preferences */ switch (xioparms.preferred_ip) { case '0': ipv[0] = PF_UNSPEC; ipv[1] = -1; break; case '4': ipv[0] = PF_INET; ipv[1] = PF_INET6; ipv[2] = -1; break; case '6': ipv[0] = PF_INET6; ipv[1] = PF_INET; ipv[2] = -1; break; default: Error("INTERNAL: undefined preferred_ip value"); return -1; } /* Create the sorted list */ ipi = 0; i = 0; while (ipv[ipi] >= 0) { themp = themlist; while (themp != NULL) { if (ipv[ipi] == PF_UNSPEC) { ai_sorted[i] = themp; ++i; } else if (ipv[ipi] == themp->ai_family) { ai_sorted[i] = themp; ++i; } themp = themp->ai_next; } ++ipi; } ai_sorted[i] = NULL; return 0; } #endif /* WITH_TCP || WITH_UDP */