mirror of
https://github.com/pocoproject/poco.git
synced 2025-01-19 08:46:41 +01:00
fix(Socket): Windows SO_REUSEADDR is neither reliable nor safe #3380
This commit is contained in:
parent
cf8e0b9cbc
commit
059b860048
@ -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);
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user