[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 = cb;
server->request_cb_arg = arg; 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) if (server->fd <= 0)
goto error; goto error;

View File

@ -35,6 +35,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/if.h> #include <net/if.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
#include <event2/event.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; fd_set fdset;
struct timeval timeout = { MDNS_CONNECT_TEST_TIMEOUT, 0 }; struct timeval timeout = { MDNS_CONNECT_TEST_TIMEOUT, 0 };
socklen_t len; socklen_t len;
int flags;
int error; int error;
int retval; int retval;
int ret; int ret;
@ -603,13 +605,29 @@ connection_test(int family, const char *address, const char *address_log, int po
return -1; 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) if (sock < 0)
{ {
DPRINTF(E_WARN, L_MDNS, "Connection test to %s:%d failed with socket error: %s\n", address_log, port, strerror(errno)); 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; 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); ret = connect(sock, ai->ai_addr, ai->ai_addrlen);
if (ret < 0 && errno != EINPROGRESS) if (ret < 0 && errno != EINPROGRESS)
{ {

View File

@ -48,6 +48,7 @@
# include <pthread_np.h> # include <pthread_np.h>
#endif #endif
#include <fcntl.h>
#include <netdb.h> #include <netdb.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <ifaddrs.h> // getifaddrs #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; return (ifname[0] != 0) ? 0 : -1;
} }
int static int
net_connect(const char *addr, unsigned short port, int type, const char *log_service_name) 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 hints = { 0 };
struct addrinfo *servinfo; struct addrinfo *servinfo;
struct addrinfo *ptr; struct addrinfo *ptr;
char strport[8]; char strport[8];
int flags;
int fd; int fd;
int ret; int ret;
DPRINTF(E_DBG, L_MISC, "Connecting to '%s' at %s (port %u)\n", log_service_name, addr, port); 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; hints.ai_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_UNSPEC : AF_INET;
snprintf(strport, sizeof(strport), "%hu", port); 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) 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) if (fd < 0)
{ {
continue; 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); 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); close(fd);
continue; continue;
@ -276,8 +290,15 @@ net_connect(const char *addr, unsigned short port, int type, const char *log_ser
return fd; 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 // 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 static int
net_bind_impl(short unsigned *port, int type, const char *log_service_name, bool reuseport) 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; const char *cfgaddr;
char addr[INET6_ADDRSTRLEN]; char addr[INET6_ADDRSTRLEN];
char strport[8]; char strport[8];
int flags;
int yes = 1; int yes = 1;
int no = 0; int no = 0;
int fd = -1; 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"); 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_family = (cfg_getbool(cfg_getsec(cfg, "general"), "ipv6")) ? AF_INET6 : AF_INET;
hints.ai_flags = cfgaddr ? 0 : AI_PASSIVE; 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) if (fd >= 0)
close(fd); 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) if (fd < 0)
continue; 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 // Makes us able to attach multiple threads to the same port
if (reuseport) if (reuseport)
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)); ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));

View File

@ -15,15 +15,6 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.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 union net_sockaddr
{ {
struct sockaddr_in sin; 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, evbase_mpd = event_base_new());
CHECK_NULL(L_MPD, cmdbase = commands_base_new(evbase_mpd, NULL)); 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) if (mpd_sockfd < 0)
{ {
DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port); DPRINTF(E_LOG, L_MPD, "Could not bind mpd server to port %hu\n", port);