| /* source: xio-listen.c */ |
| /* Copyright Gerhard Rieger 2001-2008 */ |
| /* Published under the GNU General Public License V.2, see file COPYING */ |
| |
| /* this file contains the source for listen socket options */ |
| |
| #include "xiosysincludes.h" |
| |
| #if WITH_LISTEN |
| |
| #include "xioopen.h" |
| #include "xio-named.h" |
| #include "xio-socket.h" |
| #include "xio-ip.h" |
| #include "xio-ip4.h" |
| #include "xio-listen.h" |
| #include "xio-tcpwrap.h" |
| |
| /***** LISTEN options *****/ |
| const struct optdesc opt_backlog = { "backlog", NULL, OPT_BACKLOG, GROUP_LISTEN, PH_LISTEN, TYPE_INT, OFUNC_SPEC }; |
| const struct optdesc opt_fork = { "fork", NULL, OPT_FORK, GROUP_CHILD, PH_PASTACCEPT, TYPE_BOOL, OFUNC_SPEC }; |
| /**/ |
| #if (WITH_UDP || WITH_TCP) |
| const struct optdesc opt_range = { "range", NULL, OPT_RANGE, GROUP_RANGE, PH_ACCEPT, TYPE_STRING, OFUNC_SPEC }; |
| #endif |
| |
| |
| /* |
| applies and consumes the following option: |
| PH_INIT, PH_PASTSOCKET, 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, OPT_BACKLOG, OPT_RANGE, tcpwrap, |
| OPT_SOURCEPORT, OPT_LOWPORT, cloexec |
| */ |
| int |
| xioopen_listen(struct single *xfd, int xioflags, |
| struct sockaddr *us, socklen_t uslen, |
| struct opt *opts, struct opt *opts0, |
| int pf, int socktype, int proto) { |
| int level; |
| int result; |
| |
| #if WITH_RETRY |
| if (xfd->forever || xfd->retry) { |
| level = E_INFO; |
| } else |
| #endif /* WITH_RETRY */ |
| level = E_ERROR; |
| |
| while (true) { /* loop over failed attempts */ |
| |
| /* tcp listen; this can fork() for us; it only returns on error or on |
| successful establishment of tcp connection */ |
| result = _xioopen_listen(xfd, xioflags, |
| (struct sockaddr *)us, uslen, |
| opts, pf, socktype, proto, |
| #if WITH_RETRY |
| (xfd->retry||xfd->forever)?E_INFO:E_ERROR |
| #else |
| E_ERROR |
| #endif /* WITH_RETRY */ |
| ); |
| /*! not sure if we should try again on retry/forever */ |
| switch (result) { |
| case STAT_OK: break; |
| #if WITH_RETRY |
| case STAT_RETRYLATER: |
| case STAT_RETRYNOW: |
| if (xfd->forever || xfd->retry) { |
| dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); |
| if (result == STAT_RETRYLATER) { |
| Nanosleep(&xfd->intervall, NULL); |
| } |
| dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL); |
| --xfd->retry; |
| continue; |
| } |
| return STAT_NORETRY; |
| #endif /* WITH_RETRY */ |
| default: |
| return result; |
| } |
| |
| break; |
| } /* drop out on success */ |
| |
| return result; |
| } |
| |
| |
| /* creates the listening socket, bind, applies options; waits for incoming |
| connection, checks its source address and port. Depending on fork option, it |
| may fork a subprocess. |
| pf specifies the syntax expected for range option. In the case of generic |
| socket it is 0 (expecting raw binary data), and the real pf can be obtained |
| from us->af_family; for other socket types pf == us->af_family |
| Returns 0 if a connection was accepted; with fork option, this is always in |
| a 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 in a |
| loop in the calling function (and always provide the options...) |
| After fork, we set the forever/retry of the child process to 0 |
| applies and consumes the following option: |
| PH_INIT, PH_PASTSOCKET, 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, OPT_BACKLOG, OPT_RANGE, tcpwrap, |
| OPT_SOURCEPORT, OPT_LOWPORT, cloexec |
| */ |
| int _xioopen_listen(struct single *xfd, int xioflags, struct sockaddr *us, socklen_t uslen, |
| struct opt *opts, int pf, int socktype, int proto, int level) { |
| struct sockaddr sa; |
| socklen_t salen; |
| int backlog = 5; /* why? 1 seems to cause problems under some load */ |
| char *rangename; |
| bool dofork = false; |
| char infobuff[256]; |
| char lisname[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 pas = sizeof(_peername); /* peer address size */ |
| socklen_t las = sizeof(_sockname); /* local address size */ |
| int result; |
| |
| retropt_bool(opts, OPT_FORK, &dofork); |
| |
| if (dofork) { |
| if (!(xioflags & XIO_MAYFORK)) { |
| Error("option fork not allowed here"); |
| return STAT_NORETRY; |
| } |
| xfd->flags |= XIO_DOESFORK; |
| } |
| |
| if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; |
| |
| if (dofork) { |
| xiosetchilddied(); /* set SIGCHLD handler */ |
| } |
| |
| if ((xfd->fd1 = xiosocket(opts, us->sa_family, socktype, proto, level)) < 0) { |
| return STAT_RETRYLATER; |
| } |
| xfd->fdtype = FDTYPE_SINGLE; |
| |
| applyopts(xfd->fd1, opts, PH_PASTSOCKET); |
| |
| applyopts_cloexec(xfd->fd1, opts); |
| |
| applyopts(xfd->fd1, opts, PH_PREBIND); |
| applyopts(xfd->fd1, opts, PH_BIND); |
| if (Bind(xfd->fd1, (struct sockaddr *)us, uslen) < 0) { |
| Msg4(level, "bind(%d, {%s}, "F_Zd"): %s", xfd->fd1, |
| sockaddr_info(us, uslen, infobuff, sizeof(infobuff)), uslen, |
| strerror(errno)); |
| Close(xfd->fd1); |
| return STAT_RETRYLATER; |
| } |
| |
| #if WITH_UNIX |
| if (us->sa_family == AF_UNIX) { |
| applyopts_named(((struct sockaddr_un *)us)->sun_path, opts, PH_FD); |
| } |
| #endif |
| /* under some circumstances (e.g., TCP listen on port 0) bind() fills empty |
| fields that we want to know. */ |
| salen = sizeof(sa); |
| if (Getsockname(xfd->fd1, us, &uslen) < 0) { |
| Warn4("getsockname(%d, %p, {%d}): %s", |
| xfd->fd1, &us, uslen, strerror(errno)); |
| } |
| |
| applyopts(xfd->fd1, opts, PH_PASTBIND); |
| #if WITH_UNIX |
| if (us->sa_family == AF_UNIX) { |
| /*applyopts_early(((struct sockaddr_un *)us)->sun_path, opts);*/ |
| 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 */ |
| |
| retropt_int(opts, OPT_BACKLOG, &backlog); |
| if (Listen(xfd->fd1, backlog) < 0) { |
| Error3("listen(%d, %d): %s", xfd->fd1, backlog, strerror(errno)); |
| return STAT_RETRYLATER; |
| } |
| |
| #if WITH_IP4 /*|| WITH_IP6*/ |
| if (retropt_string(opts, OPT_RANGE, &rangename) >= 0) { |
| if (xioparserange(rangename, pf, &xfd->para.socket.range) |
| < 0) { |
| free(rangename); |
| return STAT_NORETRY; |
| } |
| free(rangename); |
| xfd->para.socket.dorange = true; |
| } |
| #endif |
| |
| #if (WITH_TCP || WITH_UDP) && WITH_LIBWRAP |
| xio_retropt_tcpwrap(xfd, opts); |
| #endif /* && (WITH_TCP || WITH_UDP) && WITH_LIBWRAP */ |
| |
| #if WITH_TCP || WITH_UDP |
| if (retropt_ushort(opts, OPT_SOURCEPORT, &xfd->para.socket.ip.sourceport) >= 0) { |
| xfd->para.socket.ip.dosourceport = true; |
| } |
| retropt_bool(opts, OPT_LOWPORT, &xfd->para.socket.ip.lowport); |
| #endif /* WITH_TCP || WITH_UDP */ |
| |
| retropt_int(opts, OPT_BACKLOG, &backlog); |
| if (Listen(xfd->fd1, backlog) < 0) { |
| Error3("listen(%d, %d): %s", xfd->fd1, backlog, strerror(errno)); |
| return STAT_RETRYLATER; |
| } |
| |
| if (xioopts.logopt == 'm') { |
| Info("starting accept loop, switching to syslog"); |
| diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y'; |
| } else { |
| Info("starting accept loop"); |
| } |
| while (true) { /* but we only loop if fork option is set */ |
| char peername[256]; |
| char sockname[256]; |
| int ps; /* peer socket */ |
| salen = sizeof(struct sockaddr); |
| |
| do { |
| /*? int level = E_ERROR;*/ |
| Notice1("listening on %s", sockaddr_info(us, uslen, lisname, sizeof(lisname))); |
| ps = Accept(xfd->fd1, (struct sockaddr *)&sa, &salen); |
| if (ps >= 0) { |
| /*0 Info4("accept(%d, %p, {"F_Zu"}) -> %d", xfd->fd1, &sa, salen, ps);*/ |
| break; /* success, break out of loop */ |
| } |
| if (errno == EINTR) { |
| continue; |
| } |
| if (errno == ECONNABORTED) { |
| Notice4("accept(%d, %p, {"F_Zu"}): %s", |
| xfd->fd1, &sa, salen, strerror(errno)); |
| continue; |
| } |
| Msg4(level, "accept(%d, %p, {"F_Zu"}): %s", |
| xfd->fd1, &sa, salen, strerror(errno)); |
| Close(xfd->fd1); |
| return STAT_RETRYLATER; |
| } while (true); |
| applyopts_cloexec(ps, opts); |
| if (Getpeername(ps, &pa->soa, &pas) < 0) { |
| Warn4("getpeername(%d, %p, {"F_socklen"}): %s", |
| ps, pa, pas, strerror(errno)); |
| pa = NULL; |
| } |
| if (Getsockname(ps, &la->soa, &las) < 0) { |
| Warn4("getsockname(%d, %p, {"F_socklen"}): %s", |
| ps, la, las, strerror(errno)); |
| la = NULL; |
| } |
| Notice2("accepting connection from %s on %s", |
| sockaddr_info(pa?&pa->soa:NULL, pas, peername, sizeof(peername)), |
| sockaddr_info(pa?&la->soa:NULL, las, sockname, sizeof(sockname))); |
| |
| if (pa != NULL && la != NULL && xiocheckpeer(xfd, pa, la) < 0) { |
| if (Shutdown(ps, 2) < 0) { |
| Info2("shutdown(%d, 2): %s", ps, strerror(errno)); |
| } |
| continue; |
| } |
| |
| Info1("permitting connection from %s", |
| sockaddr_info((struct sockaddr *)pa, pas, |
| infobuff, sizeof(infobuff))); |
| |
| applyopts(xfd->fd1, opts, PH_FD); |
| applyopts(xfd->fd1, opts, PH_CONNECTED); |
| |
| if (dofork) { |
| pid_t pid; /* mostly int; only used with fork */ |
| if ((pid = xio_fork(false, level==E_ERROR?level:E_WARN)) < 0) { |
| Close(xfd->fd1); |
| return STAT_RETRYLATER; |
| } |
| if (pid == 0) { /* child */ |
| pid_t cpid = Getpid(); |
| |
| Info1("just born: client process "F_pid, cpid); |
| xiosetenvulong("PID", cpid, 1); |
| |
| if (Close(xfd->fd1) < 0) { |
| Info2("close(%d): %s", xfd->fd1, strerror(errno)); |
| } |
| xfd->fd1 = ps; |
| |
| #if WITH_RETRY |
| /* !? */ |
| xfd->forever = false; xfd->retry = 0; |
| level = E_ERROR; |
| #endif /* WITH_RETRY */ |
| |
| #if WITH_UNIX |
| /* with UNIX sockets: only listening parent is allowed to remove |
| the socket file */ |
| xfd->opt_unlink_close = false; |
| #endif /* WITH_UNIX */ |
| |
| break; |
| } |
| |
| /* server: continue loop with listen */ |
| /* shutdown() closes the socket even for the child process, but |
| close() does what we want */ |
| if (Close(ps) < 0) { |
| Info2("close(%d): %s", ps, strerror(errno)); |
| } |
| Info("still listening"); |
| } else { |
| if (Close(xfd->fd1) < 0) { |
| Info2("close(%d): %s", xfd->fd1, strerror(errno)); |
| } |
| xfd->fd1 = ps; |
| break; |
| } |
| } |
| if ((result = _xio_openlate(xfd, opts)) < 0) |
| return result; |
| |
| /* set the env vars describing the local and remote sockets */ |
| xiosetsockaddrenv("SOCK", la, las, proto); |
| xiosetsockaddrenv("PEER", pa, pas, proto); |
| |
| return 0; |
| } |
| |
| #endif /* WITH_LISTEN */ |