Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this program not receive the expected UDP packets?

I am trying to receive a UDP packet using Boost asio. My code is based off of this blocking UDP client example from the asio documentation.

I am trying to receive a BOOTP-like UDP packet from a C6655 TI DSP, which are being transmitted in 3 second intervals. I have Wireshark watching the same interface my program is listening on, and it can see the packets arriving (see below for the exact packet data, exported from Wireshark). The packets aren't really coming from the DSP, I captured one with tcpdump and I'm simulating it from a Raspberry Pi with packeth.

However, my program does not receive the packets. It has a 4 second timeout (since the DSP broadcasts every 3 seconds). If it hits the timeout, it prints a message to that effect, otherwise it's supposed to print the number of bytes received. The full (compilable) source code of the program follows (about 100 lines).

The command is being invoked with the parameters 192.168.5.122 67 4000, which means listen on 192.168.5.122:67 with a 4000 millisecond timeout.

Edit: In addition to the code below, I also tried this as my endpoint: udp::endpoint listen_endpoint(boost::asio::ip::address_v4::any(), atoi(argv[2])); as well as the IP address 0.0.0.0 as suggested by a search result somewhere.

I also added the following to no avail:

boost::asio::socket_base::broadcast option(true);
socket_.set_option(option);

I do have a program that is able to properly receive this packet, written using Berkeley sockets. It's not doing anything special that I can see, beyond binding to INADDR_ANY.

Here is the complete program:

//
// blocking_udp_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~
//
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/udp.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <iostream>

using boost::asio::deadline_timer;
using boost::asio::ip::udp;

class listener
{
public:
    listener(const udp::endpoint& listen_endpoint)
        : socket_(io_service_, listen_endpoint)
        , deadline_(io_service_)
    {
        deadline_.expires_at(boost::posix_time::pos_infin);
        check_deadline();
    }

    std::size_t receive(const boost::asio::mutable_buffer& buffer, boost::posix_time::time_duration timeout, boost::system::error_code& ec)
    {
        deadline_.expires_from_now(timeout);
        ec = boost::asio::error::would_block;
        std::size_t length = 0;
        socket_.async_receive(boost::asio::buffer(buffer), boost::bind(&listener::handle_receive, _1, _2, &ec, &length));

        // TODO: The following do/while is hinky. Does run_one() need to happen before the comparison?
        do io_service_.run_one();
        while (ec == boost::asio::error::would_block);

        return length;
    }

private:
    void check_deadline()
    {
        if (deadline_.expires_at() <= deadline_timer::traits_type::now())
        {
            // cancel() won't work on XP. Something about using close() instead... Look it up. I'm doing this on Win10.
            socket_.cancel();
            deadline_.expires_at(boost::posix_time::pos_infin);
        }
        deadline_.async_wait(boost::bind(&listener::check_deadline, this));
    }

    static void handle_receive(const boost::system::error_code& ec, std::size_t length, boost::system::error_code* out_ec, std::size_t* out_length)
    {
        *out_ec = ec;
        *out_length = length;
    }

private:
    boost::asio::io_service io_service_;
    udp::socket socket_;
    deadline_timer deadline_;
};

int main(int argc, char* argv[])
{
    try
    {
        if (argc != 4)
        {
            std::cerr << "Usage: blocking_udp_timeout <listen_addr> <listen_port> <timeout_ms>\n";
            return 1;
        }

        udp::endpoint listen_endpoint(boost::asio::ip::address::from_string("0.0.0.0"), atoi(argv[2]));
        std::cout << "Endpoint: " << listen_endpoint << std::endl;

        auto timeout = atoi(argv[3]);
        std::cout << "Timeout : " << timeout << std::endl;

        listener c(listen_endpoint);

        for (;;)
        {
            char data[1024];
            boost::system::error_code ec;
            auto n = c.receive(boost::asio::buffer(data), boost::posix_time::milliseconds{timeout}, ec);

            if (ec)
            {
                std::cout << "Receive error: " << ec.message() << "\n";
            }
            else
            {
                std::cout << "Received " << n << " bytes." << std::endl;
            }
        }
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

Here is the packet that I'm trying to receive. This includes the Ethernet frame:

0000   ff ff ff ff ff ff c4 ed ba aa 28 35 08 00 45 00  ..........(5..E.
0010   01 48 00 01 00 00 10 11 a9 a5 00 00 00 00 00 00  .H..............
0020   00 00 00 44 00 43 01 34 00 00 01 01 06 00 12 34  ...D.C.4.......4
0030   56 78 00 01 00 00 00 00 00 00 00 00 00 00 00 00  Vx..............
0040   00 00 00 00 00 00 c4 ed ba aa 28 35 00 00 00 00  ..........(5....
0050   00 00 00 00 00 00 74 69 2d 62 6f 6f 74 2d 74 61  ......ti-boot-ta
0060   62 6c 65 2d 73 76 72 00 00 00 00 00 00 00 00 00  ble-svr.........
0070   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0080   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0090   00 00 00 00 00 00 74 69 2d 62 6f 6f 74 2d 74 61  ......ti-boot-ta
00a0   62 6c 65 2d 30 30 30 37 00 00 00 00 00 00 00 00  ble-0007........
00b0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00c0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00d0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00e0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00f0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0100   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0110   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0120   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0130   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0140   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0150   00 00 00 00 00 00                                ......

I do have a Berkeley socket implementation that can receive this packet (I have removed error handling and other misc. code):

{
    struct sockaddr_in servaddr;
    socklen_t len;
    char mesg[RECV_BUFFER_LENGTH];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(67);
    bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    n = recvfrom(sockfd, mesg, RECV_BUFFER_LENGTH, 0, NULL, &len);
}
like image 430
Steve Avatar asked Oct 03 '15 00:10

Steve


People also ask

Why do UDP packets get dropped?

The UDP packet loss is especially affected by TCP traffic and its flow control mechanism. This is because TCP flow control continues to increase its window size until packet loss occurs if the advertised window size is large enough.

How common is UDP packet loss?

Packet loss happens for multiple reasons. Primarily it is caused by errors on individual links and network congestion. Packet loss due to errors on the link is very low, when links are working properly. Less than 0.01% is not unusual.

Can UDP packets get corrupted?

Short answer: YES.

Does UDP wait for acknowledgement?

Sending UDP DataThe UDP protocol does not wait for any acknowledgement and is unable to detect any lost packets. When acknowledgement or detection is required, it must be done by the application layer. However, it is better to use a TCP Socket for communication when acknowledgement is necessary.


1 Answers

Consider what happens after socket_.cancel(), but before the next call to socket_.async_receive(). If any data arrives, there is no "receive handler" assigned to the socket. When the timer expires, it causes run_once() to be called 3 times (because each async_wait(), expires_at() and some of the others cause cancellations of already-assigned handlers and cause the already-assigned handlers to be posted to the run queue with the error code operation_aborted).

You haven't mentioned what your timeout is set to. The example on which you base your code (the one from Boost documentation) sets the timeout to 10 seconds, but you make it configurable. If the timeout is too tight, this would cause a spin (post->cancel previous->call previous handler->post->etc.) and possibly call socket_.cancel() while receiving the packet. If that's the case, you wouldn't have to go so far as to test it with a broadcast. You'd be able to see it with a point-to-point connection, too.

Edit: while using a long timeout (4000ms) and your exact code, I was able to receive a sent broadcast. I had to install "traditional" netcat because BSD netcat is broken. But the line below worked:

echo "hello world" | nc.traditional -b -u 192.168.XXX.255 1500

XXX.255 is not literally "XXX.255". It's my local broadcast address. -b option in nc.bsd is broken (you can see why if you strace nc.bsd with above options).

unix stackexchange nc.traditional has a good example of how others figured out why nc.bsd cannot do UDP broadcasts (-b option does nothing).

PackEth wasn't able to to send broadcasts or p-t-p traffic, so I wouldn't use it as a measure of whether you can send broadcast traffic. Admittedly, I don't have much experience with it, so I don't know if it's broken or if I don't have it configured right.

like image 162
Dmitry Rubanovich Avatar answered Oct 13 '22 07:10

Dmitry Rubanovich