fix(Socket): Windows SO_REUSEADDR is neither reliable nor safe #3380

This commit is contained in:
Alex Fabijanic 2021-08-21 12:54:35 +02:00
parent cf8e0b9cbc
commit 059b860048
5 changed files with 197 additions and 10 deletions

View File

@ -169,6 +169,9 @@ public:
/// Returns the number of bytes available that can be read
/// without causing the socket to block.
int getError() const;
/// Returns the socket error.
void setSendBufferSize(int size);
/// Sets the size of the send buffer.
@ -496,6 +499,14 @@ inline int Socket::available() const
}
inline int Socket::getError() const
{
poco_assert_dbg(POCO_NEW_STATE_ON_MOVE && _pImpl);
return _pImpl->getError();
}
inline void Socket::setSendBufferSize(int size)
{
poco_assert_dbg(POCO_NEW_STATE_ON_MOVE && _pImpl);

View File

@ -281,6 +281,9 @@ public:
Type type();
/// Returns the socket type.
virtual int getError();
/// Returns the socket error.
virtual void setSendBufferSize(int size);
/// Sets the size of the send buffer.

View File

@ -214,10 +214,8 @@ void SocketImpl::bind(const SocketAddress& address, bool reuseAddress, bool reus
{
init(address.af());
}
if (reuseAddress)
setReuseAddress(true);
if (reusePort)
setReusePort(true);
setReuseAddress(reuseAddress);
setReusePort(reusePort);
#if defined(POCO_VXWORKS)
int rc = ::bind(_sockfd, (sockaddr*) address.addr(), address.length());
#else
@ -248,10 +246,8 @@ void SocketImpl::bind6(const SocketAddress& address, bool reuseAddress, bool reu
#else
if (ipV6Only) throw Poco::NotImplementedException("IPV6_V6ONLY not defined.");
#endif
if (reuseAddress)
setReuseAddress(true);
if (reusePort)
setReusePort(true);
setReuseAddress(reuseAddress);
setReusePort(reusePort);
int rc = ::bind(_sockfd, address.addr(), address.length());
if (rc != 0) error(address.toString());
#else
@ -754,6 +750,14 @@ bool SocketImpl::poll(const Poco::Timespan& timeout, int mode)
}
int SocketImpl::getError()
{
int result;
getOption(SOL_SOCKET, SO_ERROR, result);
return result;
}
void SocketImpl::setSendBufferSize(int size)
{
setOption(SOL_SOCKET, SO_SNDBUF, size);
@ -1022,14 +1026,25 @@ void SocketImpl::setReuseAddress(bool flag)
{
int value = flag ? 1 : 0;
setOption(SOL_SOCKET, SO_REUSEADDR, value);
#ifdef POCO_OS_FAMILY_WINDOWS
value = flag ? 0 : 1;
setOption(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, value);
#endif
}
bool SocketImpl::getReuseAddress()
{
bool ret = false;
int value(0);
getOption(SOL_SOCKET, SO_REUSEADDR, value);
return value != 0;
ret = (value != 0);
#ifdef POCO_OS_FAMILY_WINDOWS
value = 0;
getOption(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, value);
ret = ret && (value == 0);
#endif
return ret;
}

View File

@ -13,7 +13,6 @@
#include "CppUnit/TestSuite.h"
#include "UDPEchoServer.h"
#include "Poco/Net/DatagramSocket.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/NetworkInterface.h"
#include "Poco/Net/NetException.h"
#include "Poco/Timespan.h"
@ -153,6 +152,158 @@ void DatagramSocketTest::testUnbound()
}
Poco::UInt16 DatagramSocketTest::getFreePort(SocketAddress::Family family, Poco::UInt16 port)
{
bool failed = false;
poco_assert_dbg(port > 0);
--port;
DatagramSocket sock(family);
do
{
failed = false;
SocketAddress sa(family, ++port);
try
{
sock.bind(sa, false);
}
catch (Poco::Net::NetException&)
{
failed = true;
}
} while (failed && sock.getError() == POCO_EADDRINUSE);
return port;
}
void DatagramSocketTest::testReuseAddressPortWildcard()
{
Poco::UInt16 port = getFreePort(SocketAddress::IPv4, 1234);
Poco::UInt16 port6 = getFreePort(SocketAddress::IPv6, 1234);
assertTrue(port >= 1234);
assertTrue(port6 >= 1234);
// reuse
{
DatagramSocket ds1(SocketAddress::IPv4);
ds1.bind(SocketAddress(port), true);
assertTrue(ds1.getReuseAddress());
DatagramSocket ds2;
ds2.bind(SocketAddress(port), true);
assertTrue(ds2.getReuseAddress());
#ifdef POCO_HAVE_IPv6
DatagramSocket ds3(SocketAddress::IPv6);
ds3.bind6(SocketAddress(SocketAddress::IPv6, port6), true, true, false);
assertTrue(ds3.getReuseAddress());
#endif
}
#ifdef POCO_HAVE_IPv6
{
DatagramSocket ds1(SocketAddress::IPv6);
ds1.bind6(SocketAddress(SocketAddress::IPv6, port6), true, true, false);
assertTrue(ds1.getReuseAddress());
DatagramSocket ds2;
ds2.bind6(SocketAddress(SocketAddress::IPv6, port6), true, true, false);
assertTrue(ds2.getReuseAddress());
DatagramSocket ds3;
ds3.bind(SocketAddress(port), true, true);
assertTrue(ds3.getReuseAddress());
}
#endif
#ifdef POCO_HAVE_IPv6
{
DatagramSocket ds1(SocketAddress::IPv6);
ds1.bind6(SocketAddress(SocketAddress::IPv6, port), true, true, true);
assertTrue(ds1.getReuseAddress());
DatagramSocket ds2;
ds2.bind6(SocketAddress(SocketAddress::IPv6, port), true, true, true);
assertTrue(ds2.getReuseAddress());
}
#endif
// not reuse
{
DatagramSocket ds1(SocketAddress::IPv4);
ds1.bind(SocketAddress(port), false);
assertTrue(!ds1.getReuseAddress());
DatagramSocket ds2;
try
{
ds2.bind(SocketAddress(port), false);
fail("binding to non-reuse address must throw");
}
catch (Poco::IOException&) {}
#ifdef POCO_HAVE_IPv6
{
DatagramSocket ds1(SocketAddress::IPv6);
ds1.bind6(SocketAddress(SocketAddress::IPv6, port), false, false, true);
assertTrue(!ds1.getReuseAddress());
DatagramSocket ds2(SocketAddress::IPv6);
try
{
ds2.bind6(SocketAddress(SocketAddress::IPv6, port), false, false, true);
fail("binding to non-reuse address must throw");
}
catch (Poco::IOException&) {}
}
#endif
}
}
void DatagramSocketTest::testReuseAddressPortSpecific()
{
Poco::UInt16 port = getFreePort(SocketAddress::IPv4, 1234);
assertTrue(port >= 1234);
// reuse
{
DatagramSocket ds1(SocketAddress::IPv4);
ds1.bind(SocketAddress(port), true);
assertTrue(ds1.getReuseAddress());
DatagramSocket ds2;
ds2.bind(SocketAddress("127.0.0.1", port), true);
assertTrue(ds2.getReuseAddress());
#ifdef POCO_HAVE_IPv6
DatagramSocket ds3(SocketAddress::IPv6);
ds3.bind6(SocketAddress("::1", port), true, true, false);
assertTrue(ds3.getReuseAddress());
#endif
}
// not reuse
{
DatagramSocket ds1(SocketAddress::IPv4);
ds1.bind(SocketAddress("0.0.0.0", port), false);
assertTrue(!ds1.getReuseAddress());
DatagramSocket ds2;
try
{
ds2.bind(SocketAddress("127.0.0.1", port), false);
fail("binding to non-reuse IPv4 address must throw");
}
catch (Poco::IOException&) {}
#ifdef POCO_HAVE_IPv6
{
DatagramSocket ds1(SocketAddress::IPv6);
ds1.bind6(SocketAddress("::", port), false, false, true);
assertTrue(!ds1.getReuseAddress());
DatagramSocket ds2(SocketAddress::IPv6);
try
{
ds2.bind6(SocketAddress("::1", port), false, false, true);
fail("binding to non-reuse IPv6 address must throw");
}
catch (Poco::IOException&) {}
}
#endif
}
}
void DatagramSocketTest::testBroadcast()
{
UDPEchoServer echoServer;
@ -651,6 +802,8 @@ CppUnit::Test* DatagramSocketTest::suite()
CppUnit_addTest(pSuite, DatagramSocketTest, testEchoBuffer);
CppUnit_addTest(pSuite, DatagramSocketTest, testSendToReceiveFrom);
CppUnit_addTest(pSuite, DatagramSocketTest, testUnbound);
CppUnit_addTest(pSuite, DatagramSocketTest, testReuseAddressPortWildcard);
CppUnit_addTest(pSuite, DatagramSocketTest, testReuseAddressPortSpecific);
#if (POCO_OS != POCO_OS_FREE_BSD) // works only with local net bcast and very randomly
CppUnit_addTest(pSuite, DatagramSocketTest, testBroadcast);
#endif

View File

@ -15,6 +15,7 @@
#include "Poco/Net/Net.h"
#include "Poco/Net/SocketAddress.h"
#include "CppUnit/TestCase.h"
@ -29,6 +30,8 @@ public:
void testEchoBuffer();
void testSendToReceiveFrom();
void testUnbound();
void testReuseAddressPortWildcard();
void testReuseAddressPortSpecific();
void testBroadcast();
void testGatherScatterFixed();
void testGatherScatterVariable();
@ -39,6 +42,8 @@ public:
static CppUnit::Test* suite();
private:
static Poco::UInt16 getFreePort(Poco::Net::SocketAddress::Family family, std::uint16_t port);
// "STRF" are sendto/recvfrom versions of the same functionality
void testGatherScatterFixedWin();
void testGatherScatterSTRFFixedWin();