diff --git a/src/lib/bio/fd.c b/src/lib/bio/fd.c index dadf3040993c..fe298d4430a5 100644 --- a/src/lib/bio/fd.c +++ b/src/lib/bio/fd.c @@ -961,7 +961,7 @@ static ssize_t fr_bio_fd_read_accept(fr_bio_t *bio, void *packet_ctx, void *buff } -int fr_bio_fd_init_accept(fr_bio_fd_t *my) +int fr_bio_fd_init_listen(fr_bio_fd_t *my) { my->bio.read = fr_bio_fd_read_accept; my->bio.write = fr_bio_null_write; @@ -1262,7 +1262,7 @@ int fr_bio_fd_connect_full(fr_bio_t *bio, fr_event_list_t *el, fr_bio_callback_t * The caller may just call us without caring about what the underlying BIO is. In which case we * need to be safe. */ - if ((my->info.socket.af == AF_FILE_BIO) || (my->info.type == FR_BIO_FD_ACCEPT)) { + if ((my->info.socket.af == AF_FILE_BIO) || (my->info.type == FR_BIO_FD_LISTEN)) { fr_bio_fd_set_open(my); goto connected; } @@ -1363,6 +1363,7 @@ int fr_bio_fd_write_only(fr_bio_t *bio) break; case FR_BIO_FD_CONNECTED: + case FR_BIO_FD_ACCEPTED: /* * Further reads are disallowed. */ @@ -1372,7 +1373,7 @@ int fr_bio_fd_write_only(fr_bio_t *bio) } break; - case FR_BIO_FD_ACCEPT: + case FR_BIO_FD_LISTEN: fr_strerror_const("Only unconnected sockets can be marked 'write-only'"); return -1; } @@ -1380,3 +1381,106 @@ int fr_bio_fd_write_only(fr_bio_t *bio) my->bio.read = fr_bio_fd_read_discard; return 0; } + +/** Alternative to calling fr_bio_read() on new socket. + * + */ +int fr_bio_fd_accept(TALLOC_CTX *ctx, fr_bio_t **out_p, fr_bio_t *bio) +{ + int fd, tries = 0; + int rcode; + fr_bio_fd_t *my = talloc_get_type_abort(bio, fr_bio_fd_t); + socklen_t salen; + struct sockaddr_storage sockaddr; + fr_bio_fd_t *out; + fr_bio_fd_config_t *cfg; + + salen = sizeof(sockaddr); + *out_p = NULL; + + fr_assert(my->info.type == FR_BIO_FD_LISTEN); + fr_assert(my->info.socket.type == SOCK_STREAM); + +retry: +#ifdef __linux__ + /* + * Set these flags immediately on the new socket. + */ + fd = accept4(my->info.socket.fd, (struct sockaddr *) &sockaddr, &salen, SOCK_NONBLOCK | SOCK_CLOEXEC); +#else + fd = accept(my->info.socket.fd, (struct sockaddr *) &sockaddr, &salen); +#endif + if (fd < 0) { + switch (errno) { + case EINTR: + /* + * Try a few times before giving up. + */ + tries++; + if (tries <= my->max_tries) goto retry; + return 0; + + /* + * We can ignore these errors. + */ + case ECONNABORTED: +#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) + case EWOULDBLOCK: +#endif + case EAGAIN: +#ifdef EPERM + case EPERM: +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: +#endif + return 0; + + default: + /* + * Some other error, it's fatal. + */ + fr_bio_shutdown(&my->bio); + break; + } + + return fr_bio_error(IO); + } + + /* + * Allocate the base BIO and set it up. + */ + out = (fr_bio_fd_t *) fr_bio_fd_alloc(ctx, NULL, my->offset); + if (!out) { + close(fd); + return fr_bio_error(GENERIC); + } + + /* + * We have a file descriptor. Initialize the configuration with the new information. + */ + cfg = talloc_memdup(out, my->info.cfg, sizeof(*my->info.cfg)); + if (!cfg) { + fr_strerror_const("Out of memory"); + close(fd); + talloc_free(out); + return fr_bio_error(GENERIC); + } + + /* + * Set the type to ACCEPTED, and set up the rest of the callbacks to match. + */ + cfg->type = FR_BIO_FD_ACCEPTED; + out->info.socket.fd = fd; + + rcode = fr_bio_fd_open(bio, cfg); + if (rcode < 0) { + talloc_free(out); + return rcode; + } + + fr_assert(out->info.type == FR_BIO_FD_CONNECTED); + + *out_p = (fr_bio_t *) out; + return 1; +} diff --git a/src/lib/bio/fd.h b/src/lib/bio/fd.h index 6bbdf83a60e6..de40208e60fb 100644 --- a/src/lib/bio/fd.h +++ b/src/lib/bio/fd.h @@ -63,8 +63,9 @@ typedef enum { // updates #fr_bio_fd_packet_ctx_t for reads, // uses #fr_bio_fd_packet_ctx_t for writes FR_BIO_FD_CONNECTED, //!< connected client sockets (UDP or TCP) - FR_BIO_FD_ACCEPT, //!< returns new fd in buffer on fr_bio_read() + FR_BIO_FD_LISTEN, //!< returns new fd in buffer on fr_bio_read() or fr_bio_fd_accept() // updates #fr_bio_fd_packet_ctx_t on successful FD read. + FR_BIO_FD_ACCEPTED, //!< temporarily until it's connected. } fr_bio_fd_type_t; /** Configuration for sockets @@ -139,3 +140,5 @@ int fr_bio_fd_open(fr_bio_t *bio, fr_bio_fd_config_t const *cfg) CC_HINT(nonnul int fr_bio_fd_write_only(fr_bio_t *bio) CC_HINT(nonnull); int fr_bio_fd_reopen(fr_bio_t *bio) CC_HINT(nonnull); + +int fr_bio_fd_accept(TALLOC_CTX *ctx, fr_bio_t **out, fr_bio_t *bio) CC_HINT(nonnull); diff --git a/src/lib/bio/fd_open.c b/src/lib/bio/fd_open.c index 899e313a7eb8..c9ec70f70cf0 100644 --- a/src/lib/bio/fd_open.c +++ b/src/lib/bio/fd_open.c @@ -751,10 +751,19 @@ int fr_bio_fd_open(fr_bio_t *bio, fr_bio_fd_config_t const *cfg) } } - fd = socket(my->info.socket.af, my->info.socket.type, protocol); - if (fd < 0) { - fr_strerror_printf("Failed opening socket: %s", fr_syserror(errno)); - return -1; + /* + * It's already opened, so we don't need to do that. + */ + if (cfg->type == FR_BIO_FD_ACCEPTED) { + fd = my->info.socket.fd; + fr_assert(fd >= 0); + + } else { + fd = socket(my->info.socket.af, my->info.socket.type, protocol); + if (fd < 0) { + fr_strerror_printf("Failed opening socket: %s", fr_syserror(errno)); + return -1; + } } } else if (cfg->path) { @@ -928,10 +937,31 @@ int fr_bio_fd_open(fr_bio_t *bio, fr_bio_fd_config_t const *cfg) if (fr_bio_fd_init_connected(my) < 0) goto fail; break; + case FR_BIO_FD_ACCEPTED: +#ifdef SO_NOSIGPIPE + /* + * Although the server ignore SIGPIPE, some operating systems like BSD and OSX ignore the + * ignoring. + * + * Fortunately, those operating systems usually support SO_NOSIGPIPE. We set that to prevent + * them raising the signal in the first place. + */ + { + int on = 1; + + setsockopt(my->info.socket.fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); + } +#endif + + my->info.type = FR_BIO_FD_CONNECTED; + + if (fr_bio_fd_init_common(my) < 0) goto fail; + break; + /* * Server socket which listens for new stream connections */ - case FR_BIO_FD_ACCEPT: + case FR_BIO_FD_LISTEN: fr_assert(my->info.socket.type == SOCK_STREAM); switch (my->info.socket.af) { @@ -956,7 +986,7 @@ int fr_bio_fd_open(fr_bio_t *bio, fr_bio_fd_config_t const *cfg) goto fail; } - if (fr_bio_fd_init_accept(my) < 0) goto fail; + if (fr_bio_fd_init_listen(my) < 0) goto fail; break; } diff --git a/src/lib/bio/fd_priv.h b/src/lib/bio/fd_priv.h index 00c981f26f5d..def9c60b8288 100644 --- a/src/lib/bio/fd_priv.h +++ b/src/lib/bio/fd_priv.h @@ -64,6 +64,6 @@ int fr_bio_fd_init_common(fr_bio_fd_t *my); int fr_bio_fd_init_connected(fr_bio_fd_t *my); -int fr_bio_fd_init_accept(fr_bio_fd_t *my); +int fr_bio_fd_init_listen(fr_bio_fd_t *my); int fr_bio_fd_socket_name(fr_bio_fd_t *my); diff --git a/src/lib/bio/network.c b/src/lib/bio/network.c index 48fda491369c..706c10c109be 100644 --- a/src/lib/bio/network.c +++ b/src/lib/bio/network.c @@ -133,9 +133,10 @@ fr_bio_t *fr_bio_network_alloc(TALLOC_CTX *ctx, fr_ipaddr_t const *allow, fr_ipa break; case FR_BIO_FD_CONNECTED: + case FR_BIO_FD_ACCEPTED: return NULL; - case FR_BIO_FD_ACCEPT: + case FR_BIO_FD_LISTEN: break; } diff --git a/src/listen/control/proto_control_unix.c b/src/listen/control/proto_control_unix.c index 0c3acde40a47..f76145658fe4 100644 --- a/src/listen/control/proto_control_unix.c +++ b/src/listen/control/proto_control_unix.c @@ -412,7 +412,7 @@ static int mod_open(fr_listen_t *li) fr_assert(!thread->connection); cfg = (fr_bio_fd_config_t) { - .type = FR_BIO_FD_ACCEPT, + .type = FR_BIO_FD_LISTEN, .socket_type = SOCK_STREAM, .path = inst->filename, .uid = inst->uid,