[misc] Use fcntl+O_NONBLOCK when binding instead of socket+SOCK_NONBLOCK

socket() with SOCK_NONBLOCK (O_NONBLOCK) seems not to be possible on MacOS, it
yields 'Protocol wrong type for socket'. Switch to using fcntl() and O_NONBLOCK
instead, hopefully works better cross-platform.

Closes #1644
This commit is contained in:
ejurgensen 2023-08-31 17:16:22 +02:00
parent 9d092c983b
commit 54c2667aea
5 changed files with 63 additions and 20 deletions

View File

@ -359,7 +359,7 @@ httpd_server_new(struct event_base *evbase, unsigned short port, httpd_request_c
server->request_cb = cb;
server->request_cb_arg = arg;
server->fd = net_bind_with_reuseport(&port, SOCK_STREAM | SOCK_NONBLOCK, "httpd");
server->fd = net_bind_with_reuseport(&port, SOCK_STREAM, "httpd");
if (server->fd <= 0)
goto error;

View File

@ -35,6 +35,7 @@
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>
#include <fcntl.h>
#include <event2/event.h>
@ -584,6 +585,7 @@ connection_test(int family, const char *address, const char *address_log, int po
fd_set fdset;
struct timeval timeout = { MDNS_CONNECT_TEST_TIMEOUT, 0 };
socklen_t len;
int flags;
int error;
int retval;
int ret;
@ -603,13 +605,29 @@ connection_test(int family, const char *address, const char *address_log, int po
return -1;
}
sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, ai->ai_protocol);
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock < 0)
{
DPRINTF(E_WARN, L_MDNS, "Connection test to %s:%d failed with socket error: %s\n", address_log, port, strerror(errno));
goto out_free_ai;
}
// For Linux we could just give SOCK_NONBLOCK to socket(), but that won't work
// with MacOS, so we have to use fcntl()
flags = fcntl(sock, F_GETFL, 0);
if (flags < 0)
{
DPRINTF(E_WARN, L_MDNS, "Connection test to %s:%d failed with fcntl get flags error: %s\n", address_log, port, strerror(errno));
goto out_close_socket;
}
ret = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
if (ret < 0)
{
DPRINTF(E_WARN, L_MDNS, "Connection test to %s:%d failed with fcntl set flags error: %s\n", address_log, port, strerror(errno));
goto out_close_socket;
}
ret = connect(sock, ai->ai_addr, ai->ai_addrlen);
if (ret < 0 && errno != EINPROGRESS)
{

View File

@ -48,6 +48,7 @@
# include <pthread_np.h>
#endif
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ifaddrs.h> // getifaddrs
@ -222,19 +223,20 @@ net_if_get(char *ifname, size_t ifname_len, const char *addr)
return (ifname[0] != 0) ? 0 : -1;
}
int
net_connect(const char *addr, unsigned short port, int type, const char *log_service_name)
static int
net_connect_impl(const char *addr, unsigned short port, int type, const char *log_service_name, bool set_nonblock)
{
struct addrinfo hints = { 0 };
struct addrinfo *servinfo;
struct addrinfo *ptr;
char strport[8];
int flags;
int fd;
int ret;
DPRINTF(E_DBG, L_MISC, "Connecting to '%s' at %s (port %u)\n", log_service_name, addr, port);
hints.ai_socktype = (type & (SOCK_STREAM | SOCK_DGRAM)); // filter since type can be SOCK_STREAM | SOCK_NONBLOCK
hints.ai_socktype = type;
hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_UNSPEC : AF_INET;
snprintf(strport, sizeof(strport), "%hu", port);
@ -247,14 +249,26 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
for (ptr = servinfo; ptr; ptr = ptr->ai_next)
{
fd = socket(ptr->ai_family, type | SOCK_CLOEXEC, ptr->ai_protocol);
fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (fd < 0)
{
continue;
}
// For Linux we could just give SOCK_CLOEXEC to socket(), but that won't
// work with MacOS, so we have to use fcntl()
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
continue;
if (set_nonblock)
ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC);
else
ret = fcntl(fd, F_SETFL, flags | O_CLOEXEC);
if (ret < 0)
continue;
ret = connect(fd, ptr->ai_addr, ptr->ai_addrlen);
if (ret < 0 && errno != EINPROGRESS) // EINPROGRESS in case of SOCK_NONBLOCK
if (ret < 0 && errno != EINPROGRESS) // EINPROGRESS in case of nonblock
{
close(fd);
continue;
@ -276,8 +290,15 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
return fd;
}
int
net_connect(const char *addr, unsigned short port, int type, const char *log_service_name)
{
return net_connect_impl(addr, port, type, log_service_name, false);
}
// If *port is 0 then a random port will be assigned, and *port will be updated
// with the port number
// with the port number. SOCK_STREAM type services are set to use non-blocking
// sockets.
static int
net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool reuseport)
{
@ -289,6 +310,7 @@ net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool
const char *cfgaddr;
char addr[INET6_ADDRSTRLEN];
char strport[8];
int flags;
int yes = 1;
int no = 0;
int fd = -1;
@ -296,7 +318,7 @@ net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool
cfgaddr = cfg_getstr(cfg_getsec(cfg, "general"), "bind_address");
hints.ai_socktype = (type & (SOCK_STREAM | SOCK_DGRAM)); // filter since type can be SOCK_STREAM | SOCK_NONBLOCK
hints.ai_socktype = type;
hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_INET6 : AF_INET;
hints.ai_flags = cfgaddr ? 0 : AI_PASSIVE;
@ -313,10 +335,22 @@ net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool
if (fd >= 0)
close(fd);
fd = socket(ptr->ai_family, type | SOCK_CLOEXEC, ptr->ai_protocol);
fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (fd < 0)
continue;
// For Linux we could just give SOCK_NONBLOCK and SOCK_CLOEXEC to
// socket(), but that won't work with MacOS, so we have to use fcntl()
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
continue;
if (type == SOCK_STREAM)
ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK | O_CLOEXEC);
else
ret = fcntl(fd, F_SETFL, flags | O_CLOEXEC);
if (ret < 0)
continue;
// Makes us able to attach multiple threads to the same port
if (reuseport)
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));

View File

@ -15,15 +15,6 @@
#include <sys/socket.h>
#include <netinet/in.h>
#ifndef SOCK_NONBLOCK
#include <fcntl.h>
#define SOCK_NONBLOCK O_NONBLOCK
#endif
#ifndef SOCK_CLOEXEC
#define SOCK_CLOEXEC 0
#endif
union net_sockaddr
{
struct sockaddr_in sin;

View File

@ -4813,7 +4813,7 @@ mpd_init(void)
CHECK_NULL(L_MPD, evbase_mpd = event_base_new());
CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL));
mpd_sockfd = net_bind(&port, SOCK_STREAM | SOCK_NONBLOCK, "mpd");
mpd_sockfd = net_bind(&port, SOCK_STREAM, "mpd");
if (mpd_sockfd < 0)
{
DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port);