How can I inherit a socket in a child process on Windows using Boost.Asio and Boost.Procees?

Viewed 149

I am writing a multiprocess socket server, and on Linux it works perfectly, but on Windows it just crashes without any error. I believe it is because it does not actually inherit the socket file descriptor correctly.

Below is a stripped down version of my code that just prints all the file descriptors in the child process. On my windows VM it prints a list of FDs, but the FD that it gets passed from the parent process is not in the list, it's higher than any of the listed ones.

I found this windows documentation about how to inherit a socket: https://docs.microsoft.com/en-us/windows/win32/sysinfo/handle-inheritance

I also found that Boost.Process has this property, that suggests by default handlers are inherited: https://www.boost.org/doc/libs/1_78_0/doc/html/boost/process/limit_handles.html

But It's not clear why my socket isn't getting inherited, and what to do about it.

#include <stdio.h>
#include <string> 
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <boost/process/handles.hpp>


using boost::this_process::native_handle_type;
using namespace boost::asio;
using ip::tcp;

int main(int argc, char const *argv[])
{
    int port = 5923;
    if (argc == 2 && strcmp(argv[1], "--help")==0) {
        printf("%s [port]\n", argv[0]);
        return 0;
    } else if (argc == 3 && strcmp(argv[1], "--child")==0) {
        printf("child process\n");
        int fd = std::stoi(argv[2]);
        auto hds = boost::this_process::get_handles();
        for (auto h : hds) {
            std::cout << h << std::endl;
        }
        printf("got FD %d\n", fd);
        return 0;
    } else if (argc == 2)
    {
        port = std::stoi(argv[1]);
    }
    boost::asio::io_service io_service;
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v6(), port));
    while (1)
    {
        printf("waiting for clients\n");
        tcp::socket peersocket(io_service);
        acceptor.accept(peersocket);
        auto endpoint = peersocket.remote_endpoint();
        printf("Accepted new connection from a client %s:%d\n", endpoint.address(), endpoint.port());
        std::string fd = std::to_string(peersocket.release());
        std::cout << fd << std::endl;
        boost::process::spawn(argv[0], "--child", fd);
    }
    return 0;
}
1 Answers

Okay I found the answer, it's actually super simple EOF

Jk, I'll leave the solution for future me or other lost souls.

You have to write an "extension" to make Windows inherit the handles. https://www.boost.org/doc/libs/1_78_0/doc/html/boost_process/extend.html

The properties of the windows_executor are explained here: https://www.boost.org/doc/libs/1_78_0/doc/html/boost/process/extend/windows_executor.html

So all we need to do is register a on_setup extension and set inherit_handles to true. Something like this:

struct do_inherit : boost::process::extend::handler
{
    template<typename Char, typename Sequence>
    void on_setup(ex::windows_executor<Char, Sequence> & exec)
    {
        std::cout << "windows setup" << std::endl;
        exec.inherit_handles = 1;
    }

    template<typename Sequence>
    void on_setup(ex::posix_executor<Sequence> & exec)
    {
        std::cout << "unix setup" << std::endl;
    }
};

And with all this modern technology we can finally do what inetd did decades ago, but now with more template generics and on Windows.

Related