我的程序应该同时从生成的 TAP 设备读取数据包并处理它们。为此,我使用 LaKabane 的 tuntap 库以及 Boost.Asio 的
posix::stream_descriptor
。
但是,由于我充当客户端而不是服务器,因此无法选择异步接受数据包。
我选择的临时解决方案是一次又一次异步读取。然而,这有两个主要问题:
sudo ping -f ff02::1%test
#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;
}
(完整源码
) 我现在的问题是,如何使用 Boost.Asio 从 TAP 设备读取数据而不丢失数据包?
std::array<char, 1520> buffer;
tap_device.async_read_some( //
asio::buffer(buffer), [&](boost::system::error_code const& error, std::size_t bytes_transferred) {
start(tap_device);
handle_packet(error, bytes_transferred, buffer);
});
首先,这不是
递归。完成处理程序是一个“延续”。根据定义,它是在异步操作完成时调用的。所以新的 start
永远不会重叠。此外,它不在启动函数的堆栈帧中执行。相反,它在服务线程上执行。
buffer
是局部变量。它的生命周期在 async_read_some
启动后立即结束,因此
根据定义在它完成之前。因此,当它在完成处理程序中使用时,它就变得无效了。因此,由于代码已被破坏,让我们忽略有关速度的有缺陷的观察。首先,让我们修复它。有多种方法,但在我看来,这种方法最具指导性/可扩展性:
struct Client {
using Handler = std::function<void(error_code, std::string)>;
Client(asio::any_io_executor ex, tun_tap& dev, Handler handler = default_handler)
: tap_device_(ex, dev.native_handle())
, handler_(std::move(handler)) {
start();
}
private:
stream_descriptor tap_device_;
Handler handler_;
std::array<char, 1520> buffer_{};
void start() {
tap_device_.async_read_some( //
asio::buffer(buffer_), //
[this](error_code const& error, size_t bytes_transferred) {
if (error) {
std::cerr << "Error: " << error.message() << std::endl;
} else {
std::string packet(buffer_.data(), bytes_transferred);
start();
if (handler_) {
handler_(error, std::move(packet));
}
};
});
}
static void default_handler(error_code ec, std::string const& packet) {
if (ec) {
std::cerr << "Error: " << ec.message() << std::endl;
} else {
std::cout << "Received packet: " << packet.size() << " bytes" << std::endl;
}
}
};
int main() try {
constexpr uint16_t mtu = 1500;
asio::io_context io;
tun_tap dev{"test", tun_tap_mode::tap};
dev.set_mtu(mtu);
dev.up();
Client c(io.get_executor(), dev);
io.run();
} catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl << "Exit program.";
return 1;
}
我还审查了 tun_tap.hpp/cpp 修复了一些问题和泄漏:
文件
tun_tap.hpp
#ifndef TUNTAP_HPP
#define TUNTAP_HPP
#include <cstdint>
#include <string>
#include <tuntap.h>
#include <memory>
enum class tun_tap_mode { tun = 0, tap };
class tun_tap {
public:
tun_tap(std::string const& ifname, tun_tap_mode const& mode);
void set_ip(std::string const& ip, uint8_t netmask);
void set_mtu(uint16_t mtu);
void up();
void down();
int native_handle();
private:
struct Destroy final {
constexpr void operator()(device* dev) const noexcept {
if (dev)
::tuntap_destroy(dev);
}
};
std::unique_ptr<device, Destroy> _device;
};
#endif
文件
tun_tap.cpp
#include "tun_tap.hpp"
#include <linux/if.h>
#include <stdexcept>
tun_tap::tun_tap(std::string const& ifname, tun_tap_mode const& mode) {
if (ifname.empty())
throw std::invalid_argument("ifname");
if (ifname.size() > IFNAMSIZ)
throw std::invalid_argument("ifname");
if (mode != tun_tap_mode::tun && mode != tun_tap_mode::tap)
throw std::invalid_argument("tun_tap_mode");
_device.reset(tuntap_init());
int m = mode == tun_tap_mode::tun ? TUNTAP_MODE_TUNNEL : TUNTAP_MODE_ETHERNET;
if (::tuntap_start(_device.get(), m, TUNTAP_ID_ANY))
throw std::runtime_error("Failed to start tuntap device.");
if (::tuntap_set_ifname(_device.get(), ifname.c_str()))
throw std::runtime_error("Failed to set ifname for tuntap device.");
}
void tun_tap::up() {
if (::tuntap_up(_device.get()))
throw std::runtime_error("Failed to bring tuntap device up.");
}
void tun_tap::down() {
if (::tuntap_down(_device.get()))
throw std::runtime_error("Failed to bring tuntap device down.");
}
void tun_tap::set_mtu(uint16_t mtu) {
if (::tuntap_set_mtu(_device.get(), mtu))
throw std::runtime_error("Failed to set mtu for tuntap device.");
}
void tun_tap::set_ip(std::string const& ip, uint8_t netmask) {
if (netmask > 128) // TODO FIXME? seems 32 should be the max due to ipv4
throw std::invalid_argument("netmask");
if (::tuntap_set_ip(_device.get(), ip.c_str(), netmask))
throw std::runtime_error("Failed to set ip address for tuntap device.");
}
int tun_tap::native_handle() { //
return ::tuntap_get_fd(_device.get());
}
这里效果很好: