Explorar o código

Merge pull request #570 from paullouisageneau/udp-mux

ICE UDP multiplexing support
Paul-Louis Ageneau %!s(int64=3) %!d(string=hai) anos
pai
achega
796c336087

+ 2 - 0
DOC.md

@@ -81,6 +81,7 @@ typedef struct {
 	rtcCertificateType certificateType;
 	rtcTransportPolicy iceTransportPolicy;
 	bool enableIceTcp;
+	bool enableIceUdpMux;
 	bool disableAutoNegotiation;
 	uint16_t portRangeBegin;
 	uint16_t portRangeEnd;
@@ -100,6 +101,7 @@ Arguments:
   - `certificateType` (optional): certificate type, either `RTC_CERTIFICATE_ECDSA` or `RTC_CERTIFICATE_RSA` (0 or `RTC_CERTIFICATE_DEFAULT` if default)
   - `iceTransportPolicy` (optional): ICE transport policy, if set to `RTC_TRANSPORT_POLICY_RELAY`, the PeerConnection will emit only relayed candidates (0 or `RTC_TRANSPORT_POLICY_ALL` if default)
   - `enableIceTcp`: if true, generate TCP candidates for ICE (ignored with libjuice as ICE backend)
+  - `enableIceUdpMux`: if true, connections are multiplexed on the same UDP port (should be combined with `portRangeBegin` and `portRangeEnd`, ignored with libnice as ICE backend)
   - `disableAutoNegotiation`: if true, the user is responsible for calling `rtcSetLocalDescription` after creating a Data Channel and after setting the remote description
   - `portRangeBegin` (optional): first port (included) of the allowed local port range (0 if unused)
   - `portRangeEnd` (optional): last port (included) of the allowed local port (0 if unused)

+ 1 - 1
deps/libjuice

@@ -1 +1 @@
-Subproject commit ab1d0d0cd8597b3f356f3f2e700b06289ebcacd5
+Subproject commit 6950fb1a50484b76571a4806d028b5e206baed20

+ 5 - 0
examples/client/main.cpp

@@ -72,6 +72,11 @@ int main(int argc, char **argv) try {
 		config.iceServers.emplace_back(stunServer);
 	}
 
+	if (params.udpMux()) {
+		cout << "ICE UDP mux enabled" << endl;
+		config.enableIceUdpMux = true;
+	}
+
 	localId = randomId(4);
 	cout << "The local ID is: " << localId << endl;
 

+ 7 - 1
examples/client/parse_cl.cpp

@@ -44,6 +44,7 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
   static struct option long_options[] =
   {
     {"noStun", no_argument, NULL, 'n'},
+    {"udpMux", no_argument, NULL, 'm'},
     {"stunServer", required_argument, NULL, 's'},
     {"stunPort", required_argument, NULL, 't'},
     {"webSocketServer", required_argument, NULL, 'w'},
@@ -56,6 +57,7 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
 
   /* default values */
   _n = false;
+  _m = false;
   _s = "stun.l.google.com";
   _t = 19302;
   _w = "localhost";
@@ -63,7 +65,7 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
   _h = false;
 
   optind = 0;
-  while ((c = getopt_long (argc, argv, "s:t:w:x:enhv", long_options, &optind)) != - 1)
+  while ((c = getopt_long (argc, argv, "s:t:w:x:enmhv", long_options, &optind)) != - 1)
     {
       switch (c)
         {
@@ -71,6 +73,10 @@ Cmdline::Cmdline (int argc, char *argv[]) // ISO C++17 not allowed: throw (std::
           _n = true;
           break;
 
+		case 'm':
+		  _m = true;
+		  break;
+
         case 's':
           _s = optarg;
           break;

+ 2 - 0
examples/client/parse_cl.h

@@ -35,6 +35,7 @@ class Cmdline
 private:
   /* parameters */
   bool _n;
+  bool _m;
   std::string _s;
   int _t;
   std::string _w;
@@ -57,6 +58,7 @@ public:
   int next_param () { return _optind; }
 
   bool noStun () const { return _n; }
+  bool udpMux () const { return _m; }
   std::string stunServer () const { return _s; }
   int stunPort () const { return _t; }
   std::string webSocketServer () const { return _w; }

+ 2 - 1
include/rtc/configuration.hpp

@@ -80,7 +80,8 @@ struct RTC_CPP_EXPORT Configuration {
 	// Options
 	CertificateType certificateType = CertificateType::Default;
 	TransportPolicy iceTransportPolicy = TransportPolicy::All;
-	bool enableIceTcp = false;
+	bool enableIceTcp = false;    // libnice only
+	bool enableIceUdpMux = false; // libjuice only
 	bool disableAutoNegotiation = false;
 
 	// Port range

+ 2 - 1
include/rtc/rtc.h

@@ -155,7 +155,8 @@ typedef struct {
 	const char *bindAddress; // libjuice only, NULL means any
 	rtcCertificateType certificateType;
 	rtcTransportPolicy iceTransportPolicy;
-	bool enableIceTcp;
+	bool enableIceTcp;    // libnice only
+	bool enableIceUdpMux; // libjuice only
 	bool disableAutoNegotiation;
 	uint16_t portRangeBegin; // 0 means automatic
 	uint16_t portRangeEnd;   // 0 means automatic

+ 2 - 0
pages/content/pages/reference.md

@@ -84,6 +84,7 @@ typedef struct {
 	rtcCertificateType certificateType;
 	rtcTransportPolicy iceTransportPolicy;
 	bool enableIceTcp;
+	bool enableIceUdpMux;
 	bool disableAutoNegotiation;
 	uint16_t portRangeBegin;
 	uint16_t portRangeEnd;
@@ -103,6 +104,7 @@ Arguments:
   - `certificateType` (optional): certificate type, either `RTC_CERTIFICATE_ECDSA` or `RTC_CERTIFICATE_RSA` (0 or `RTC_CERTIFICATE_DEFAULT` if default)
   - `iceTransportPolicy` (optional): ICE transport policy, if set to `RTC_TRANSPORT_POLICY_RELAY`, the PeerConnection will emit only relayed candidates (0 or `RTC_TRANSPORT_POLICY_ALL` if default)
   - `enableIceTcp`: if true, generate TCP candidates for ICE (ignored with libjuice as ICE backend)
+  - `enableIceUdpMux`: if true, connections are multiplexed on the same UDP port (should be combined with `portRangeBegin` and `portRangeEnd`, ignored with libnice as ICE backend)
   - `disableAutoNegotiation`: if true, the user is responsible for calling `rtcSetLocalDescription` after creating a Data Channel and after setting the remote description
   - `portRangeBegin` (optional): first port (included) of the allowed local port range (0 if unused)
   - `portRangeEnd` (optional): last port (included) of the allowed local port (0 if unused)

+ 2 - 0
src/capi.cpp

@@ -365,6 +365,7 @@ int rtcCreatePeerConnection(const rtcConfiguration *config) {
 		c.certificateType = static_cast<CertificateType>(config->certificateType);
 		c.iceTransportPolicy = static_cast<TransportPolicy>(config->iceTransportPolicy);
 		c.enableIceTcp = config->enableIceTcp;
+		c.enableIceUdpMux = config->enableIceUdpMux;
 		c.disableAutoNegotiation = config->disableAutoNegotiation;
 
 		if (config->mtu > 0)
@@ -1431,3 +1432,4 @@ int rtcSetSctpSettings(const rtcSctpSettings *settings) {
 		return RTC_ERR_SUCCESS;
 	});
 }
+

+ 19 - 7
src/impl/icetransport.cpp

@@ -42,9 +42,9 @@ using std::chrono::system_clock;
 
 namespace rtc::impl {
 
-#if !USE_NICE
+#if !USE_NICE // libjuice
 
-#define MAX_TURN_SERVERS_COUNT 2
+const int MAX_TURN_SERVERS_COUNT = 2;
 
 IceTransport::IceTransport(const Configuration &config, candidate_callback candidateCallback,
                            state_callback stateChangeCallback,
@@ -56,9 +56,6 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
       mAgent(nullptr, nullptr) {
 
 	PLOG_DEBUG << "Initializing ICE transport (libjuice)";
-	if (config.enableIceTcp) {
-		PLOG_WARNING << "ICE-TCP is not supported with libjuice";
-	}
 
 	juice_log_level_t level;
 	auto logger = plog::get();
@@ -93,6 +90,17 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 	jconfig.cb_recv = IceTransport::RecvCallback;
 	jconfig.user_ptr = this;
 
+	if (config.enableIceTcp) {
+		PLOG_WARNING << "ICE-TCP is not supported with libjuice";
+	}
+
+	if (config.enableIceUdpMux) {
+		PLOG_DEBUG << "Enabling ICE UDP mux";
+		jconfig.concurrency_mode = JUICE_CONCURRENCY_MODE_MUX;
+	} else {
+		jconfig.concurrency_mode = JUICE_CONCURRENCY_MODE_POLL;
+	}
+
 	// Randomize servers order
 	std::vector<IceServer> servers = config.iceServers;
 	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
@@ -167,7 +175,7 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
 	// setup:actpass.
 	// See https://tools.ietf.org/html/rfc5763#section-5
 	Description desc(string(sdp), type,
-	                   type == Description::Type::Offer ? Description::Role::ActPass : mRole);
+	                 type == Description::Type::Offer ? Description::Role::ActPass : mRole);
 	desc.addIceOption("trickle");
 	return desc;
 }
@@ -420,6 +428,10 @@ IceTransport::IceTransport(const Configuration &config, candidate_callback candi
 		             nullptr);
 	}
 
+	if (config.enableIceUdpMux) {
+		PLOG_WARNING << "ICE UDP mux is not available with libnice";
+	}
+
 	// Randomize order
 	std::vector<IceServer> servers = config.iceServers;
 	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());
@@ -576,7 +588,7 @@ Description IceTransport::getLocalDescription(Description::Type type) const {
 	// setup:actpass.
 	// See https://tools.ietf.org/html/rfc5763#section-5
 	Description desc(string(sdp.get()), type,
-	                   type == Description::Type::Offer ? Description::Role::ActPass : mRole);
+	                 type == Description::Type::Offer ? Description::Role::ActPass : mRole);
 	desc.addIceOption("trickle");
 	return desc;
 }

+ 1 - 0
src/impl/icetransport.hpp

@@ -23,6 +23,7 @@
 #include "common.hpp"
 #include "configuration.hpp"
 #include "description.hpp"
+#include "global.hpp"
 #include "peerconnection.hpp"
 #include "transport.hpp"
 

+ 1 - 0
src/impl/init.cpp

@@ -23,6 +23,7 @@
 #include "dtlstransport.hpp"
 #include "pollservice.hpp"
 #include "sctptransport.hpp"
+#include "icetransport.hpp"
 #include "threadpool.hpp"
 #include "tls.hpp"
 

+ 65 - 17
src/impl/pollinterrupter.cpp

@@ -29,10 +29,54 @@
 namespace rtc::impl {
 
 PollInterrupter::PollInterrupter() {
-#ifndef _WIN32
+#ifdef _WIN32
+	struct addrinfo *ai = NULL;
+	struct addrinfo hints;
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_protocol = IPPROTO_UDP;
+	hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+	if (getaddrinfo("localhost", "0", &hints, &ai) != 0)
+		throw std::runtime_error("Resolution failed for localhost address");
+
+	try {
+		mSock = ::socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+		if (mSock == INVALID_SOCKET)
+			throw std::runtime_error("UDP socket creation failed");
+
+		// Set non-blocking
+		ctl_t nbio = 1;
+		::ioctlsocket(mSock, FIONBIO, &nbio);
+
+		// Bind
+		if (::bind(mSock, ai->ai_addr, (socklen_t)ai->ai_addrlen) < 0)
+			throw std::runtime_error("Failed to bind UDP socket");
+
+		// Connect to self
+		struct sockaddr_storage addr;
+		socklen_t addrlen = sizeof(addr);
+		if (::getsockname(mSock, reinterpret_cast<struct sockaddr *>(&addr), &addrlen) < 0)
+			throw std::runtime_error("getsockname failed");
+
+		if (::connect(mSock, reinterpret_cast<struct sockaddr *>(&addr), addrlen) < 0)
+			throw std::runtime_error("Failed to connect UDP socket");
+
+	} catch (...) {
+		freeaddrinfo(ai);
+		if (mSock != INVALID_SOCKET)
+			::closesocket(mSock);
+
+		throw;
+	}
+
+	freeaddrinfo(ai);
+
+#else
 	int pipefd[2];
 	if (::pipe(pipefd) != 0)
 		throw std::runtime_error("Failed to create pipe");
+
 	::fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
 	::fcntl(pipefd[1], F_SETFL, O_NONBLOCK);
 	mPipeOut = pipefd[1]; // read
@@ -41,10 +85,8 @@ PollInterrupter::PollInterrupter() {
 }
 
 PollInterrupter::~PollInterrupter() {
-	std::lock_guard lock(mMutex);
 #ifdef _WIN32
-	if (mDummySock != INVALID_SOCKET)
-		::closesocket(mDummySock);
+	::closesocket(mSock);
 #else
 	::close(mPipeIn);
 	::close(mPipeOut);
@@ -52,28 +94,34 @@ PollInterrupter::~PollInterrupter() {
 }
 
 void PollInterrupter::prepare(struct pollfd &pfd) {
-	std::lock_guard lock(mMutex);
 #ifdef _WIN32
-	if (mDummySock == INVALID_SOCKET)
-		mDummySock = ::socket(AF_INET, SOCK_DGRAM, 0);
-	pfd.fd = mDummySock;
-	pfd.events = POLLIN;
+	pfd.fd = mSock;
 #else
-	char dummy;
-	if (::read(mPipeIn, &dummy, 1) < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
-		PLOG_WARNING << "Reading from interrupter pipe failed, errno=" << errno;
-	}
 	pfd.fd = mPipeIn;
+#endif
 	pfd.events = POLLIN;
+}
+
+void PollInterrupter::process(struct pollfd &pfd) {
+	if (pfd.revents & POLLIN) {
+#ifdef _WIN32
+		char dummy;
+		while (::recv(pfd.fd, &dummy, 1, 0) >= 0) {
+			// Ignore
+		}
+#else
+		char dummy;
+		while (::read(pfd.fd, &dummy, 1) > 0) {
+			// Ignore
+		}
 #endif
+	}
 }
 
 void PollInterrupter::interrupt() {
-	std::lock_guard lock(mMutex);
 #ifdef _WIN32
-	if (mDummySock != INVALID_SOCKET) {
-		::closesocket(mDummySock);
-		mDummySock = INVALID_SOCKET;
+	if (::send(mSock, NULL, 0, 0) < 0 && sockerrno != SEAGAIN && sockerrno != SEWOULDBLOCK) {
+		PLOG_WARNING << "Writing to interrupter socket failed, errno=" << sockerrno;
 	}
 #else
 	char dummy = 0;

+ 5 - 4
src/impl/pollinterrupter.hpp

@@ -24,8 +24,6 @@
 
 #if RTC_ENABLE_WEBSOCKET
 
-#include <mutex>
-
 namespace rtc::impl {
 
 // Utility class to interrupt poll()
@@ -34,13 +32,16 @@ public:
 	PollInterrupter();
 	~PollInterrupter();
 
+	PollInterrupter(const PollInterrupter &other) = delete;
+	void operator=(const PollInterrupter &other) = delete;
+
 	void prepare(struct pollfd &pfd);
+	void process(struct pollfd &pfd);
 	void interrupt();
 
 private:
-	std::mutex mMutex;
 #ifdef _WIN32
-	socket_t mDummySock = INVALID_SOCKET;
+	socket_t mSock;
 #else // assume POSIX
 	int mPipeIn, mPipeOut;
 #endif

+ 53 - 55
src/impl/pollservice.cpp

@@ -40,6 +40,7 @@ PollService::~PollService() {}
 
 void PollService::start() {
 	mSocks = std::make_unique<SocketMap>();
+	mInterrupter = std::make_unique<PollInterrupter>();
 	mStopped = false;
 	mThread = std::thread(&PollService::runLoop, this);
 }
@@ -51,35 +52,35 @@ void PollService::join() {
 
 	lock.unlock();
 
-	mInterrupter.interrupt();
+	mInterrupter->interrupt();
 	mThread.join();
+
 	mSocks.reset();
+	mInterrupter.reset();
 }
 
 void PollService::add(socket_t sock, Params params) {
 	std::unique_lock lock(mMutex);
-	assert(mSocks);
-
-	mSocks->erase(sock);
-
-	if (!params.callback)
-		return;
+	assert(params.callback);
 
 	PLOG_VERBOSE << "Registering socket in poll service, direction=" << params.direction;
 	auto until = params.timeout ? std::make_optional(clock::now() + *params.timeout) : nullopt;
-	mSocks->emplace(sock, SocketEntry{std::move(params), std::move(until)});
+	assert(mSocks);
+	mSocks->insert_or_assign(sock, SocketEntry{std::move(params), std::move(until)});
 
-	mInterrupter.interrupt();
+	assert(mInterrupter);
+	mInterrupter->interrupt();
 }
 
 void PollService::remove(socket_t sock) {
 	std::unique_lock lock(mMutex);
-	assert(mSocks);
 
 	PLOG_VERBOSE << "Unregistering socket in poll service";
+	assert(mSocks);
 	mSocks->erase(sock);
 
-	mInterrupter.interrupt();
+	assert(mInterrupter);
+	mInterrupter->interrupt();
 }
 
 void PollService::prepare(std::vector<struct pollfd> &pfds, optional<clock::time_point> &next) {
@@ -88,7 +89,7 @@ void PollService::prepare(std::vector<struct pollfd> &pfds, optional<clock::time
 	next.reset();
 
 	auto it = pfds.begin();
-	mInterrupter.prepare(*it++);
+	mInterrupter->prepare(*it++);
 	for (const auto &[sock, entry] : *mSocks) {
 		it->fd = sock;
 		switch (entry.params.direction) {
@@ -111,55 +112,52 @@ void PollService::prepare(std::vector<struct pollfd> &pfds, optional<clock::time
 
 void PollService::process(std::vector<struct pollfd> &pfds) {
 	std::unique_lock lock(mMutex);
-	for (auto it = pfds.begin(); it != pfds.end(); ++it) {
+
+	auto it = pfds.begin();
+	mInterrupter->process(*it++);
+	while (it != pfds.end()) {
 		socket_t sock = it->fd;
 		auto jt = mSocks->find(sock);
-		if (jt == mSocks->end())
-			continue; // removed
-
-		try {
-			auto &entry = jt->second;
-			const auto &params = entry.params;
-
-			if (it->revents & POLLNVAL || it->revents & POLLERR) {
-				PLOG_VERBOSE << "Poll error event";
-				auto callback = std::move(params.callback);
-				mSocks->erase(sock);
-				callback(Event::Error);
-				continue;
-			}
-
-			if (it->revents & POLLIN || it->revents & POLLOUT) {
-				entry.until =
-				    params.timeout ? std::make_optional(clock::now() + *params.timeout) : nullopt;
-
-				auto callback = params.callback;
-
-				if (it->revents & POLLIN) {
-					PLOG_VERBOSE << "Poll in event";
-					params.callback(Event::In);
-				}
-
-				if (it->revents & POLLOUT) {
-					PLOG_VERBOSE << "Poll out event";
-					params.callback(Event::Out);
+		if (jt != mSocks->end()) {
+			try {
+				auto &entry = jt->second;
+				const auto &params = entry.params;
+
+				if (it->revents & POLLNVAL || it->revents & POLLERR) {
+					PLOG_VERBOSE << "Poll error event";
+					auto callback = std::move(params.callback);
+					mSocks->erase(sock);
+					callback(Event::Error);
+
+				} else if (it->revents & POLLIN || it->revents & POLLOUT) {
+					entry.until = params.timeout
+					                  ? std::make_optional(clock::now() + *params.timeout)
+					                  : nullopt;
+
+					auto callback = params.callback;
+					if (it->revents & POLLIN) {
+						PLOG_VERBOSE << "Poll in event";
+						callback(Event::In);
+					}
+					if (it->revents & POLLOUT) {
+						PLOG_VERBOSE << "Poll out event";
+						callback(Event::Out);
+					}
+
+				} else if (entry.until && clock::now() >= *entry.until) {
+					PLOG_VERBOSE << "Poll timeout event";
+					auto callback = std::move(params.callback);
+					mSocks->erase(sock);
+					callback(Event::Timeout);
 				}
 
-				continue;
-			}
-
-			if (entry.until && clock::now() >= *entry.until) {
-				PLOG_VERBOSE << "Poll timeout event";
-				auto callback = std::move(params.callback);
+			} catch (const std::exception &e) {
+				PLOG_WARNING << e.what();
 				mSocks->erase(sock);
-				callback(Event::Timeout);
-				continue;
 			}
-
-		} catch (const std::exception &e) {
-			PLOG_WARNING << e.what();
-			mSocks->erase(sock);
 		}
+
+		++it;
 	}
 }
 
@@ -180,7 +178,7 @@ void PollService::runLoop() {
 					auto msecs = duration_cast<milliseconds>(
 					    std::max(clock::duration::zero(), *next - clock::now()));
 					PLOG_VERBOSE << "Entering poll, timeout=" << msecs.count() << "ms";
-					timeout = msecs.count();
+					timeout = int(msecs.count());
 				} else {
 					PLOG_VERBOSE << "Entering poll";
 					timeout = -1;

+ 1 - 1
src/impl/pollservice.hpp

@@ -76,11 +76,11 @@ private:
 
 	using SocketMap = std::unordered_map<socket_t, SocketEntry>;
 	unique_ptr<SocketMap> mSocks;
+	unique_ptr<PollInterrupter> mInterrupter;
 
 	std::recursive_mutex mMutex;
 	std::thread mThread;
 	bool mStopped;
-	PollInterrupter mInterrupter;
 };
 
 std::ostream &operator<<(std::ostream &out, PollService::Direction direction);

+ 1 - 0
src/impl/sctptransport.hpp

@@ -21,6 +21,7 @@
 
 #include "common.hpp"
 #include "configuration.hpp"
+#include "global.hpp"
 #include "processor.hpp"
 #include "queue.hpp"
 #include "transport.hpp"

+ 12 - 9
src/impl/tcpserver.cpp

@@ -46,9 +46,10 @@ shared_ptr<TcpTransport> TcpServer::accept() {
 			break;
 
 		struct pollfd pfd[2];
-		pfd[0].fd = mSock;
-		pfd[0].events = POLLIN;
-		mInterrupter.prepare(pfd[1]);
+		mInterrupter.prepare(pfd[0]);
+		pfd[1].fd = mSock;
+		pfd[1].events = POLLIN;
+
 		lock.unlock();
 		int ret = ::poll(pfd, 2, -1);
 		lock.lock();
@@ -63,11 +64,13 @@ shared_ptr<TcpTransport> TcpServer::accept() {
 				throw std::runtime_error("Failed to wait for socket connection");
 		}
 
-		if (pfd[0].revents & POLLNVAL || pfd[0].revents & POLLERR) {
+		mInterrupter.process(pfd[0]);
+
+		if (pfd[1].revents & POLLNVAL || pfd[1].revents & POLLERR) {
 			throw std::runtime_error("Error while waiting for socket connection");
 		}
 
-		if (pfd[0].revents & POLLIN) {
+		if (pfd[1].revents & POLLIN) {
 			struct sockaddr_storage addr;
 			socklen_t addrlen = sizeof(addr);
 			socket_t incomingSock = ::accept(mSock, (struct sockaddr *)&addr, &addrlen);
@@ -106,7 +109,7 @@ void TcpServer::listen(uint16_t port) {
 	hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
 
 	struct addrinfo *result = nullptr;
-	if (::getaddrinfo(nullptr, std::to_string(port).c_str(), &hints, &result))
+	if (getaddrinfo(nullptr, std::to_string(port).c_str(), &hints, &result))
 		throw std::runtime_error("Resolution failed for local address");
 
 	try {
@@ -135,7 +138,7 @@ void TcpServer::listen(uint16_t port) {
 
 		// Enable REUSEADDR
 		::setsockopt(mSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&enabled),
-			             sizeof(enabled));
+		             sizeof(enabled));
 
 		// Listen on both IPv6 and IPv4
 		if (ai->ai_family == AF_INET6)
@@ -143,8 +146,8 @@ void TcpServer::listen(uint16_t port) {
 			             reinterpret_cast<const char *>(&disabled), sizeof(disabled));
 
 		// Set non-blocking
-		ctl_t b = 1;
-		if (::ioctlsocket(mSock, FIONBIO, &b) < 0)
+		ctl_t nbio = 1;
+		if (::ioctlsocket(mSock, FIONBIO, &nbio) < 0)
 			throw std::runtime_error("Failed to set socket non-blocking mode");
 
 		// Bind socket

+ 4 - 1
src/impl/tcpserver.hpp

@@ -29,11 +29,14 @@
 
 namespace rtc::impl {
 
-class TcpServer {
+class TcpServer final {
 public:
 	TcpServer(uint16_t port);
 	~TcpServer();
 
+	TcpServer(const TcpServer &other) = delete;
+	void operator=(const TcpServer &other) = delete;
+
 	shared_ptr<TcpTransport> accept();
 	void close();
 

+ 4 - 4
src/impl/tcptransport.cpp

@@ -48,8 +48,8 @@ TcpTransport::TcpTransport(socket_t sock, state_callback callback)
 	PLOG_DEBUG << "Initializing TCP transport with socket";
 
 	// Set non-blocking
-	ctl_t b = 1;
-	if (::ioctlsocket(mSock, FIONBIO, &b) < 0)
+	ctl_t nbio = 1;
+	if (::ioctlsocket(mSock, FIONBIO, &nbio) < 0)
 		throw std::runtime_error("Failed to set socket non-blocking mode");
 
 	// Retrieve hostname and service
@@ -221,8 +221,8 @@ void TcpTransport::prepare(const sockaddr *addr, socklen_t addrlen) {
 			throw std::runtime_error("TCP socket creation failed");
 
 		// Set non-blocking
-		ctl_t b = 1;
-		if (::ioctlsocket(mSock, FIONBIO, &b) < 0)
+		ctl_t nbio = 1;
+		if (::ioctlsocket(mSock, FIONBIO, &nbio) < 0)
 			throw std::runtime_error("Failed to set socket non-blocking mode");
 
 #ifdef __APPLE__

+ 1 - 1
test/capi_websocketserver.cpp

@@ -144,7 +144,7 @@ int test_capi_websocketserver_main() {
 	while (!success && !failed && attempts--)
 		sleep(1);
 
-	if (failed)
+	if (!success || failed)
 		goto error;
 
 	rtcDeleteWebSocket(wsclient);