/* SPDX-License-Identifier: MPL-2.0 */
#include "precompiled.hpp"
#include <new>
#include <string>
#include <stdio.h>
#include "ws_listener.hpp"
#include "io_thread.hpp"
#include "config.hpp"
#include "err.hpp"
#include "ip.hpp"
#include "tcp.hpp"
#include "socket_base.hpp"
#include "address.hpp"
#include "ws_engine.hpp"
#include "session_base.hpp"
#ifdef ZMQ_HAVE_WSS
#include "wss_engine.hpp"
#include "wss_address.hpp"
#endif
#ifndef ZMQ_HAVE_WINDOWS
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#ifdef ZMQ_HAVE_VXWORKS
#include <sockLib.h>
#endif
#endif
#ifdef ZMQ_HAVE_OPENVMS
#include <ioctl.h>
#endif
zmq::ws_listener_t::ws_listener_t (io_thread_t *io_thread_,
socket_base_t *socket_,
const options_t &options_,
bool wss_) :
stream_listener_base_t (io_thread_, socket_, options_), _wss (wss_)
{
#ifdef ZMQ_HAVE_WSS
if (_wss) {
int rc = gnutls_certificate_allocate_credentials (&_tls_cred);
zmq_assert (rc == GNUTLS_E_SUCCESS);
gnutls_datum_t cert = {(unsigned char *) options_.wss_cert_pem.c_str (),
(unsigned int) options_.wss_cert_pem.length ()};
gnutls_datum_t key = {(unsigned char *) options_.wss_key_pem.c_str (),
(unsigned int) options_.wss_key_pem.length ()};
rc = gnutls_certificate_set_x509_key_mem (_tls_cred, &cert, &key,
GNUTLS_X509_FMT_PEM);
zmq_assert (rc == GNUTLS_E_SUCCESS);
}
#endif
}
zmq::ws_listener_t::~ws_listener_t ()
{
#ifdef ZMQ_HAVE_WSS
if (_wss)
gnutls_certificate_free_credentials (_tls_cred);
#endif
}
void zmq::ws_listener_t::in_event ()
{
const fd_t fd = accept ();
// If connection was reset by the peer in the meantime, just ignore it.
// TODO: Handle specific errors like ENFILE/EMFILE etc.
if (fd == retired_fd) {
_socket->event_accept_failed (
make_unconnected_bind_endpoint_pair (_endpoint), zmq_errno ());
return;
}
int rc = tune_tcp_socket (fd);
rc = rc | tune_tcp_maxrt (fd, options.tcp_maxrt);
if (rc != 0) {
_socket->event_accept_failed (
make_unconnected_bind_endpoint_pair (_endpoint), zmq_errno ());
return;
}
// Create the engine object for this connection.
create_engine (fd);
}
std::string zmq::ws_listener_t::get_socket_name (zmq::fd_t fd_,
socket_end_t socket_end_) const
{
std::string socket_name;
#ifdef ZMQ_HAVE_WSS
if (_wss)
socket_name = zmq::get_socket_name<wss_address_t> (fd_, socket_end_);
else
#endif
socket_name = zmq::get_socket_name<ws_address_t> (fd_, socket_end_);
return socket_name + _address.path ();
}
int zmq::ws_listener_t::create_socket (const char *addr_)
{
tcp_address_t address;
_s = tcp_open_socket (addr_, options, true, true, &address);
if (_s == retired_fd) {
return -1;
}
// TODO why is this only done for the listener?
make_socket_noninheritable (_s);
// Allow reusing of the address.
int flag = 1;
int rc;
#ifdef ZMQ_HAVE_WINDOWS
// TODO this was changed for Windows from SO_REUSEADDRE to
// SE_EXCLUSIVEADDRUSE by 0ab65324195ad70205514d465b03d851a6de051c,
// so the comment above is no longer correct; also, now the settings are
// different between listener and connecter with a src address.
// is this intentional?
rc = setsockopt (_s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<const char *> (&flag), sizeof (int));
wsa_assert (rc != SOCKET_ERROR);
#elif defined ZMQ_HAVE_VXWORKS
rc =
setsockopt (_s, SOL_SOCKET, SO_REUSEADDR, (char *) &flag, sizeof (int));
errno_assert (rc == 0);
#else
rc = setsockopt (_s, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (int));
errno_assert (rc == 0);
#endif
// Bind the socket to the network interface and port.
#if defined ZMQ_HAVE_VXWORKS
rc = bind (_s, (sockaddr *) _address.addr (), _address.addrlen ());
#else
rc = bind (_s, address.addr (), address.addrlen ());
#endif
#ifdef ZMQ_HAVE_WINDOWS
if (rc == SOCKET_ERROR) {
errno = wsa_error_to_errno (WSAGetLastError ());
goto error;
}
#else
if (rc != 0)
goto error;
#endif
// Listen for incoming connections.
rc = listen (_s, options.backlog);
#ifdef ZMQ_HAVE_WINDOWS
if (rc == SOCKET_ERROR) {
errno = wsa_error_to_errno (WSAGetLastError ());
goto error;
}
#else
if (rc != 0)
goto error;
#endif
return 0;
error:
const int err = errno;
close ();
errno = err;
return -1;
}
int zmq::ws_listener_t::set_local_address (const char *addr_)
{
if (options.use_fd != -1) {
// in this case, the addr_ passed is not used and ignored, since the
// socket was already created by the application
_s = options.use_fd;
} else {
const int rc = _address.resolve (addr_, true, options.ipv6);
if (rc != 0)
return -1;
// remove the path, otherwise resolving the port will fail with wildcard
const char *delim = strrchr (addr_, '/');
std::string host_address;
if (delim) {
host_address = std::string (addr_, delim - addr_);
} else {
host_address = addr_;
}
if (create_socket (host_address.c_str ()) == -1)
return -1;
}
_endpoint = get_socket_name (_s, socket_end_local);
_socket->event_listening (make_unconnected_bind_endpoint_pair (_endpoint),
_s);
return 0;
}
zmq::fd_t zmq::ws_listener_t::accept ()
{
// The situation where connection cannot be accepted due to insufficient
// resources is considered valid and treated by ignoring the connection.
// Accept one connection and deal with different failure modes.
zmq_assert (_s != retired_fd);
struct sockaddr_storage ss;
memset (&ss, 0, sizeof (ss));
#if defined ZMQ_HAVE_HPUX || defined ZMQ_HAVE_VXWORKS
int ss_len = sizeof (ss);
#else
socklen_t ss_len = sizeof (ss);
#endif
#if defined ZMQ_HAVE_SOCK_CLOEXEC && defined HAVE_ACCEPT4
fd_t sock = ::accept4 (_s, reinterpret_cast<struct sockaddr *> (&ss),
&ss_len, SOCK_CLOEXEC);
#else
const fd_t sock =
::accept (_s, reinterpret_cast<struct sockaddr *> (&ss), &ss_len);
#endif
if (sock == retired_fd) {
#if defined ZMQ_HAVE_WINDOWS
const int last_error = WSAGetLastError ();
wsa_assert (last_error == WSAEWOULDBLOCK || last_error == WSAECONNRESET
|| last_error == WSAEMFILE || last_error == WSAENOBUFS);
#elif defined ZMQ_HAVE_ANDROID
errno_assert (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR
|| errno == ECONNABORTED || errno == EPROTO
|| errno == ENOBUFS || errno == ENOMEM || errno == EMFILE
|| errno == ENFILE || errno == EINVAL);
#else
errno_assert (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR
|| errno == ECONNABORTED || errno == EPROTO
|| errno == ENOBUFS || errno == ENOMEM || errno == EMFILE
|| errno == ENFILE);
#endif
return retired_fd;
}
make_socket_noninheritable (sock);
if (zmq::set_nosigpipe (sock)) {
#ifdef ZMQ_HAVE_WINDOWS
const int rc = closesocket (sock);
wsa_assert (rc != SOCKET_ERROR);
#else
int rc = ::close (sock);
errno_assert (rc == 0);
#endif
return retired_fd;
}
// Set the IP Type-Of-Service priority for this client socket
if (options.tos != 0)
set_ip_type_of_service (sock, options.tos);
// Set the protocol-defined priority for this client socket
if (options.priority != 0)
set_socket_priority (sock, options.priority);
return sock;
}
void zmq::ws_listener_t::create_engine (fd_t fd_)
{
const endpoint_uri_pair_t endpoint_pair (
get_socket_name (fd_, socket_end_local),
get_socket_name (fd_, socket_end_remote), endpoint_type_bind);
i_engine *engine = NULL;
if (_wss)
#ifdef ZMQ_HAVE_WSS
engine = new (std::nothrow)
wss_engine_t (fd_, options, endpoint_pair, _address, false, _tls_cred,
std::string ());
#else
zmq_assert (false);
#endif
else
engine = new (std::nothrow)
ws_engine_t (fd_, options, endpoint_pair, _address, false);
alloc_assert (engine);
// Choose I/O thread to run connecter in. Given that we are already
// running in an I/O thread, there must be at least one available.
io_thread_t *io_thread = choose_io_thread (options.affinity);
zmq_assert (io_thread);
// Create and launch a session object.
session_base_t *session =
session_base_t::create (io_thread, false, _socket, options, NULL);
errno_assert (session);
session->inc_seqnum ();
launch_child (session);
send_attach (session, engine, false);
_socket->event_accepted (endpoint_pair, fd_);
}
↑ V1077 Conditional initialization inside the 'ws_listener_t' constructor may leave some members uninitialized. Inspect the following: _tls_cred.