My program should simultaneously read packets from a generated TAP device and process them. For this I use the tuntap library from LaKabane together with Boost.Asio's posix::stream_descriptor.
However, since I am acting as a client and not a server, there is no option to accept packets asynchronously.
The proviorical solution I have chosen is to read asynchronously again and again. However, there are two major problems with this:
- There could be a stack overflow, as the same function is called "infinitely" often.
- The function does not accept the packets fast enough. I have tested this with
sudo ping -f ff02::1%test.
The following is my code so far:
#include <iostream>
#include <cstdlib>
#include <boost/asio.hpp>
#include <unistd.h>
#include "tun_tap.hpp"
void handle_packet([[maybe_unused]] const boost::system::error_code& error, [[maybe_unused]] std::size_t bytes_transferred, [[maybe_unused]] const std::array<char, 1520>& buffer)
{
if (error)
{
std::clog << "Error in handle_packet: " << error.message() << std::endl;
return;
}
std::clog << "Received packet of size: " << bytes_transferred << std::endl;
std::clog << std::flush;
// To something with the packet
sleep(5);
}
void start(boost::asio::posix::stream_descriptor& tap_device)
{
std::array<char, 1520> buffer;
tap_device.async_read_some(boost::asio::buffer(buffer),
[&](const boost::system::error_code& error, std::size_t bytes_transferred) {
start(tap_device);
handle_packet(error, bytes_transferred, buffer);
});
}
int main() {
try {
boost::asio::io_context io;
const ::size_t mtu = 1500;
std::clog << "Create TUN device." << std::endl;
tun_tap dev = tun_tap("test", tun_tap_mode::tap);
std::clog << "Set MTU to " << mtu << "." << std::endl;
dev.set_mtu(1500);
std::clog << "Set the TUN device up." << std::endl;
dev.up();
boost::asio::posix::stream_descriptor tap_device(io, ::dup(dev.native_handler()));
start(tap_device);
io.run();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl << "Exit program.";
::exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
My question now is, how can I read from the TAP device with Boost.Asio without losing packets?
Your problems are not primarily related to the device specifics. They are about C++ and object lifetime in relation to asynchronous operations.
Firstly, that's not recursion. The completion handler is a continuation. It's by definition called when the async operation completed. So the new
startnever overlaps. Also, it doesn't execute in the stack frame of the initiating function. Instead it executes on a service thread.However, that's also the problem here.
bufferis a local variable. It's lifetime ends immediately afterasync_read_someis initiated, so by definition before it completes. So, when it is used inside the completion handler it has become invalid.So, since the code is broken, let's ignore the flawed observations about speed. First, let's fix it. There are several ways, but this one seems to me to be most instructive/extensible:
I also reviewed
tun_tap.hpp/cppfixing some issues and the leak:File
tun_tap.hppFile
tun_tap.cppIt works quite nicely here: