// SPDX-License-Identifier: ISC #include #include #include #include #include #include #include #include #include #include #include static int sockflag(int sockflag, bool nonblock, bool cloexec) { if (nonblock) sockflag |= SOCK_NONBLOCK; if (cloexec) sockflag |= SOCK_CLOEXEC; return sockflag; } int gai_connect(const struct addrinfo **addr, const struct addrinfo *addrs, bool nonblock, bool cloexec, const struct dl *dl) { struct pollfd *pfds; const struct addrinfo *it; size_t i, addrs_total = 0, addrs_done = 0; int errsv = 0, ret = -1; trace_msg(GAI, "%s(%s, %s, %d, %d, %s)\n", __func__, addr ? "addr" : "NULL", addrs ? "addrs" : "NULL", nonblock, cloexec, dl ? "dl" : "NULL"); for (it = addrs; it; it = it->ai_next) addrs_total++; trace_msg(GAI, "%s: connecting to one of %zu addresses\n", __func__, addrs_total); pfds = calloc(addrs_total, sizeof(*pfds)); if (!pfds) return -1; for (i = 0; i < addrs_total; ++i) pfds[i].fd = -1; /* try to connect to all results, save the fd in pfds */ for (i = 0, it = addrs; it; i++, it = it->ai_next) { struct pollfd *pfd = &pfds[i]; int fd; fd = socket(it->ai_family, sockflag(it->ai_socktype, true, cloexec), 0); if (fd < 0) { errsv = errno; continue; } ret = connect(fd, it->ai_addr, it->ai_addrlen); if (!ret) { trace_msg(GAI, "connected to %d: early success\n", fd); ret = fd; if (addr) *addr = it; goto exit_success; } if (errno == EINPROGRESS) { pfd->fd = fd; pfd->events = POLLOUT; trace_msg(GAI, "adding %d to the waiting list\n", fd); } else { errsv = errno; close(fd); trace_msg(GAI, "skipping %d: early error: %d\n", fd, errsv); } } /* wait for the first connection to succeed or for all to fail * (addrs_done == addrs_len) */ while (ret < 0 && addrs_done < addrs_total) { trace_msg(GAI, "waiting, addrs_done = %zu, addrs_total = %zu, ret = %d\n", addrs_done, addrs_total, ret); if (poll_dl(pfds, addrs_total, dl) < 0) { errsv = errno; goto exit_cleanup; } for (i = 0, it = addrs; it; i++, it = it->ai_next) { struct pollfd *pfd = &pfds[i]; socklen_t len; if (pfd->fd < 0 || !(pfd->revents & POLLOUT)) continue; /* fetch a possible connection error */ trace_msg(GAI, "reading errno for %d\n", pfd->fd); len = sizeof(errsv); if (getsockopt(pfd->fd, SOL_SOCKET, SO_ERROR, &errsv, &len) < 0) errsv = errno; if (!errsv) { trace_msg(GAI, "connected to %d: late success\n", pfd->fd); ret = pfd->fd; if (addr) *addr = it; pfd->fd = -1; goto exit_success; } trace_msg(GAI, "got an error %d: %s, increasing nfds_done\n", errsv, strerror(errsv)); addrs_done++; close(pfd->fd); pfd->fd = -1; } } exit_success: if (ret >= 0) { trace_msg(GAI, "returning %d: success\n", ret); /* clear nonblock first if it was not requested */ if (!nonblock) { trace_msg(GAI, "clearing nonblock for %d\n", ret); if (fd_set_block(ret, true) < 0) { errsv = errno; close(ret); ret = -1; goto exit_cleanup; } } } exit_cleanup: for (i = 0; i < addrs_total; ++i) { struct pollfd *pfd = &pfds[i]; if (pfd->fd >= 0) close(pfd->fd); } free(pfds); errno = errsv; return ret; } int gai_bind(const struct addrinfo **addr, const struct addrinfo *addrs, int sock_fd, bool nonblock, bool cloexec) { int errsv = 0; const struct addrinfo *it; size_t addrs_total = 0; trace_msg(GAI, "%s(%s, %s, %d, %d, %d)\n", __func__, addr ? "addr" : "NULL", addrs ? "addrs" : "NULL", sock_fd, nonblock, cloexec); for (it = addrs; it; it = it->ai_next) addrs_total++; trace_msg(GAI, "%s: binding to one of %zu addresses\n", __func__, addrs_total); /* try to bind to all results in sequence */ for (it = addrs; it; it = it->ai_next) { int fd; if (sock_fd < 0) { fd = socket(it->ai_family, sockflag(it->ai_socktype, nonblock, cloexec), 0); if (fd < 0) { errsv = errno; continue; } } else { fd = sock_fd; } if (bind(fd, it->ai_addr, it->ai_addrlen) < 0) { errsv = errno; if (sock_fd < 0) close(fd); continue; } if (addr) *addr = it; return fd; } errno = errsv; return -1; }