Thinkal VB
Thinkal VB

Reputation: 303

Asio synchronous IPv6 UDP server

https://github.com/ThinkalVB/RTDS-Server I am making a simple UDP IPv6 server that prints version of the UDP packet sent by the remote endpoint. But this code is acting strangely. When sending IPv6 and IPv4 packets it is printing IPv6. What is that I am doing wrong? [Testing in Win10 with Packet Sender Portable 6.2.3 (127.0.0.1 and ::1)]

#include <asio.hpp>
#include <iostream>
#include "udp_peer.h"
#include <thread>


asio::io_context ioContext;
asio::io_context::work worker(ioContext);

void runServer()
{
    ioContext.run();
}

int main()
{
    asio::ip::udp::endpoint mUDPep(asio::ip::udp::v6(), 321);
    asio::ip::udp::socket mUDPsock(ioContext);
    std::thread thread(runServer);
    thread.detach();

    asio::error_code ec;
    UDPpeer udpPeer(&mUDPsock);  // Ignore this, it contains the character array
    asio::ip::udp::endpoint ep;


    mUDPsock.open(mUDPep.protocol(), ec);
    mUDPsock.bind(mUDPep, ec);
    while (true)
    {
        auto dataSize = mUDPsock.receive_from(udpPeer.getReadBuffer(), ep);
        if (ep.address().is_v4())
            std::cout << "IPv4";
        else
            std::cout << "IPv6";
    }
}

Upvotes: 2

Views: 498

Answers (1)

sehe
sehe

Reputation: 392833

You're only listening on v6.

The ep do not dictate how you receive.

Your v6 endpoint is able to receive both. Print the actual endpoint to see:

#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::udp;

int main() {
    asio::thread_pool context(1);
    udp::socket sock(context, {udp::v6(), 8888});

    udp::endpoint ep;
    char arr[4096];

    while (true) {
        /*auto n =*/ sock.receive_from(asio::buffer(arr), ep);
        std::cout 
            << std::boolalpha << ep.address().is_v4() << " "
            << ep << "\n";
    }

    context.join();
}

Now sending two packets:

echo -n "hello world $RANDOM" | nc -6 -w 0 -u ::1 8888
echo -n "hello world $RANDOM" | nc -4 -w 0 -u 127.0.0.1 8888

Prints:

false [::1]:49972
false [::ffff:127.0.0.1]:34368

For comparison, saying udp::socket sock(context, {udp::v4(), 8888}); instead simply doesn't receive the v6 packet:

true 127.0.0.1:39805

In other words, because your socket is bound to v6, the address you get is mapped as if with:

if (a.is_v4())
    return asio::ip::address_v6::v4_mapped(a.to_v4());

What to do?

Check whether the v6 is v4 mapped or compatible:

asio::ip::address_v4 a4;
if (a6.is_v4_compatible() || a6.is_v4_mapped())
    a4 = a6.to_v4();

Looks like the more modern interface for this is going to be something like

 a4 = make_address_v4(asio::ip::v4_mapped, a6);

Demo: Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::udp;

int main() {
    asio::thread_pool context(1);
    udp::socket sock(context, {udp::v6(), 8888});

    udp::endpoint ep;
    char arr[4096];

    for (auto n=2; n--;) {
        /*auto n =*/ sock.receive_from(asio::buffer(arr), ep);
        asio::ip::address_v4 a4;

        {
            auto a6 = ep.address().to_v6();
            if (a6.is_v4_compatible() || a6.is_v4_mapped())
                a4 = a6.to_v4();
        }

        std::cout
            << (a4.is_unspecified()? "not-mapped" : a4.to_string()) << " "
            << ep << "\n";
    }

    context.join();
}

Prints

127.0.0.1 [::ffff:127.0.0.1]:54859
not-mapped [::1]:36231

Upvotes: 2

Related Questions