Simple-Web-Server icon indicating copy to clipboard operation
Simple-Web-Server copied to clipboard

No exception when the server port is already in use on Windows

Open Skywalker13 opened this issue 10 years ago • 24 comments

Hello,

It's strange but I can start the server on a port which is already used and no exception is thrown.

I'm using boost 1.59

Regards

Skywalker13 avatar Nov 20 '15 14:11 Skywalker13

Ok, it's seems that it's the normal behaviour.. it's lazy.. powerful but a bit problematic in my case

Skywalker13 avatar Nov 20 '15 14:11 Skywalker13

I get terminating with uncaught exception of type boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >: bind: Address already in use, but what OS are you running the server on?

eidheim avatar Nov 20 '15 14:11 eidheim

I'm working on Windows 8.1 and toolset v120_xp (Visual Studio 2013), boost 1.59

I start a node server on the same port that SimpleWebServer. Sometimes it's the node server which catch the packets, and sometimes it's the SimpleWebServer. Maybe it's something like port sharing?! I'm not an expert for the sockets TCP.

Skywalker13 avatar Nov 20 '15 14:11 Skywalker13

I know there are some problems with exceptions on Windows, had the problems related to another project I'm working on (https://github.com/cppit/jucipp), but I have not had time to dive into it yet. Would appreciate any helpful links or pull requests towards fixing any Windows exception issues.

eidheim avatar Nov 20 '15 14:11 eidheim

~~I must do that on Windows in order to have the exception:~~

diff --git a/server_http.hpp b/server_http.hpp
index 569a618..6917773 100644
--- a/server_http.hpp
+++ b/server_http.hpp
@@ -333,6 +333,7 @@ namespace SimpleWeb {
             //Create new socket for this connection
             //Shared_ptr is used to pass temporary objects to the asynchronous functions
             std::shared_ptr<HTTP> socket(new HTTP(io_service));
+            socket->set_option(boost::asio::socket_base::reuse_address(false));

             acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec){
                 //Immediately start accepting a new connection

Sorry it's wrong :-( it fails every time...

resources:

  • http://stackoverflow.com/questions/5002761/boostasio-server-detect-failure-to-listen-to-server-port
  • http://itamarst.org/writings/win32sockets.html

Skywalker13 avatar Nov 20 '15 15:11 Skywalker13

Try add the following inside the ServerBase constructor (https://github.com/eidheim/Simple-Web-Server/blob/master/server_http.hpp#L156):

boost::asio::ip::tcp::acceptor::reuse_address option(false);
acceptor.set_option(option);

eidheim avatar Nov 20 '15 15:11 eidheim

I've tried and it changes nothing. :-(

Skywalker13 avatar Nov 20 '15 16:11 Skywalker13

See if you can make sense of http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t/14388707#14388707. If your node server and the Simple-Web-Server uses exactly the same ip:port combination you should get an exception no matter if reuse_address is true or false.

You can use boost::asio::ip::address::from_string("10.0.0.2") for instance to set a specific address instead of using boost::asio::ip::tcp::v4() at https://github.com/eidheim/Simple-Web-Server/blob/master/server_http.hpp#L155.

Try set the same address on both the node server and Simple-Web-Server.

eidheim avatar Nov 20 '15 17:11 eidheim

I will, by the way, add another constructor most likely, that has the address as first parameter. So that this becomes simpler, and one can run this server on multiple networking cards.

eidheim avatar Nov 20 '15 17:11 eidheim

I'll commit a solution for the things discussed above Tomorrow, using a Server::options class that one can set before running Server::start. One of the options will be reuse_address that will be set correctly.

eidheim avatar Nov 21 '15 17:11 eidheim

Hello,

Thanks for all

Then yes, when I set the same address on node and on server_http then an exception is thrown. If node is binding on 0.0.0.0 then the exception is never thrown even is reuse_addr is set explicitly to false.

I even tested with something like (without success)

typedef boost::asio::detail::socket_option::boolean<BOOST_ASIO_OS_DEF(SOL_SOCKET), SO_EXCLUSIVEADDRUSE> excluse_address;
acceptor.set_option(excluse_address(true));

But I'm not able to have a case where it fails everytime.

Skywalker13 avatar Nov 23 '15 08:11 Skywalker13

Did you try the latest commit? Boost::Asio is very picky on where you set the options.

eidheim avatar Nov 23 '15 08:11 eidheim

Ok I've a case where it works as expected..

If server_http binds on 0.0.0.0, reuse_address is false and excluse_address is true, the exception is always thrown (tested with node on 0.0.0.0 and 127.0.0.1).

I can live with that but for me it's a security problem because I want to bind server_http only on 127.0.0.1 with the same behaviour.

Skywalker13 avatar Nov 23 '15 08:11 Skywalker13

Yes I've tried with the latest version

Skywalker13 avatar Nov 23 '15 08:11 Skywalker13

The working case:

    this->server->config.address = "0.0.0.0";
    this->server->config.reuse_address = false;

(only valid with Windows)

diff --git a/server_http.hpp b/server_http.hpp
index afb36c0..8d3cc48 100644
--- a/server_http.hpp
+++ b/server_http.hpp
@@ -141,6 +141,10 @@ namespace SimpleWeb {
                 endpoint=std::unique_ptr<boost::asio::ip::tcp::endpoint>(new boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port));
             acceptor.open(endpoint->protocol());
             acceptor.set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
+
+            typedef boost::asio::detail::socket_option::boolean<BOOST_ASIO_OS_DEF(SOL_SOCKET), SO_EXCLUSIVEADDRUSE> excluse_address;
+            acceptor.set_option(excluse_address(true));
+
             acceptor.bind(*endpoint);
             acceptor.listen();

Skywalker13 avatar Nov 23 '15 08:11 Skywalker13

I will have to read up on this some more, but as I understand it now: On Windows, if Server::config.reuse_address == false, also set the SO_EXCLUSIVEADDRUSE socket option?

eidheim avatar Nov 23 '15 08:11 eidheim

The exception is always thrown (with my tests) if:

  1. Server::config.reuse_address == false
  2. SO_EXCLUSIVEADDRUSE == true
  3. Server::config.address = "0.0.0.0"
  4. The other server (node for example) binds on 0.0.0.0 or 127.0.0.1

Skywalker13 avatar Nov 23 '15 09:11 Skywalker13

The SO_EXCLUSIVEADDRUSE flag smells just like a hack

#define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) /* disallow local address reuse */

Then I've tried with:

int optval = 1;
auto native_socket = acceptor.native_handle();
if (::setsockopt(native_socket,
                 SOL_SOCKET,
                 SO_EXCLUSIVEADDRUSE,
                 reinterpret_cast<const char *>(&optval), sizeof(optval)) != 0)
{ /* error management */

}

But the bind() is still possible when my node server is already listening on the same port.

I don't understand and I've no idea how to fix it.

Skywalker13 avatar Nov 23 '15 13:11 Skywalker13

From what I can understand from http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t/14388707#14388707: "Microsoft realized that this might be a problem and thus added another socket option SO_EXCLUSIVEADDRUSE", you need to set SO_EXCLUSIVEADDRUSE on the node server. Is that possible?

eidheim avatar Nov 23 '15 13:11 eidheim

I've tested with only two node servers, the first one uses the wildcard and the second one the loopback IP (both the same port).

Both server can be started (without exception) and it's not always the same server that catch the paquets. Then the problem is global... I'm not sure that I can do something around this problem.

Skywalker13 avatar Nov 23 '15 14:11 Skywalker13

I was maybe a bit unclear in my previous post, from http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t/14388707#14388707 (scroll down to Windows): "Setting SO_EXCLUSIVEADDRUSE on a socket makes sure that if the binding succeeds, the combination of source address and port is owned exclusively by this socket and no other socket can bind to them, not even if it has SO_REUSEADDR set.". So if you set SO_EXCLUSIVEADDRUSE on an application (the main application which is running node), then no other application can bind to the same address(any combination):port.

For instance if you set SO_EXCLUSIVEADDRUSE on Simple-Web-Server, then no other application, Simple-Web-Server or any node application, can bind to this address:port. The challenge, though, is to set SO_EXCLUSIVEADDRUSE on the node application.

This is at least my understanding of the problem. If possible though, I would use Debian stable as server OS, but that is a personal preference, and maybe not possible in your case.

eidheim avatar Nov 23 '15 17:11 eidheim

I think that as a security measure, and to make sure any networking server performs as in a posix environment, SO_EXCLUSIVEADDRUSE should always be turned on in Windows. I will most likely add this, but again, I have to read more on this. Reopening this issue.

eidheim avatar Nov 23 '15 18:11 eidheim

I've tried with SO_EXCLUSIVEADDRUSE on Simple-Web-Server, bind on 127.0.0.1:3333

The node server can bind on 0.0.0.0:3333 without error and it's the problem. It's not working like the documentation. If the node server is started before Simple-Web-Server with the same settings, Simple-Web-Server binds without error too; but it should be exclusive. The error happens only if the node server uses 127.0.0.1 like Simple-Web-Server.

I can provide a complete archive with all files in order to reproduce the problem.

Skywalker13 avatar Nov 24 '15 07:11 Skywalker13

For what I can understand, this is a rather huge security issue for Windows, but what if the different serverapplications are running on different user logins? I'm afraid I do not have any other ideas if SO_EXCLUSIVEADDRUSE is not working. Maybe other than setting the SO_EXCLUSIVEADDRUSE option correctly, if it is not set correctly already for some reason. Like I said, boost is rather picky on how you set these options.

eidheim avatar Nov 24 '15 11:11 eidheim