使用原始套接字重新传输大数据包

Retransmitting large packets with raw sockets

问题:在原始套接字上,recvfrom 可以捕获比 sendto 可以发送的更多字节,阻止我重新传输大于 MTU 的数据包。

背景:我正在编写一个应用程序来捕获和重新传输数据包。基本上主机 A 将数据发送到 X,记录它们并将它们转发给 B,所有 Linux 机器。我正在使用原始套接字,所以我可以捕获所有数据,它是用 socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)).

创建的

然后,有等待和读取传入数据包的代码:

const int buffer_size = 2048;
uint8_t* buffer = new uint8_t[buffer_size];
sockaddr_ll addr = {0};
socklen_t addr_len = sizeof(addr);
int received_bytes = recvfrom(_raw_socket, buffer, buffer_size, 0, (struct sockaddr*)&addr, &addr_len);

随后进行数据包处理,循环结束,再次发送数据包:

struct sockaddr_ll addr;
memset(&addr, 0, sizeof(struct sockaddr_ll));
addr.sll_family = htons(AF_PACKET);
addr.sll_protocol = eth_hdr->type;
addr.sll_ifindex = interface().id();
addr.sll_halen = HardwareAddress::byte_size;
memcpy(&(addr.sll_addr), eth_hdr->dest_mac, HardwareAddress::byte_size);

// Try to send packet
if(sendto(raw_socket(), data, length, 0, (struct sockaddr*)&addr, sizeof(addr)) < 0)

问题是我不希望收到大于以太网 MTU(1500 字节)的数据包,而且我不应该这样做,因为我使用的是单独处理每个数据包的原始套接字。但有时我确实会收到大于 MTU 的数据包。我认为这可能是我的代码中的错误,但 Wireshark 证实了这一点,如图所示,因此必须在较低级别(如网络控制器本身)进行一些重组。

好吧,那么我认为没有办法仅针对一个应用程序禁用此功能,而且我无法更改主机配置,因此我可能会增加缓冲区大小。但问题是,当我用大于 MTU 大小的任何东西调用 sendto 时(实际上是 1514B,因为 eth header),我得到 80: Message too long errno。这就是上面提到的问题——我无法发送我收到的同一个数据包。这可能是什么解决方案?我需要多大的缓冲区才能始终捕获整个数据包?

编辑:我刚刚检查了带有ethtool -k interf的机器并在所有机器上都得到了tcp-segmentation-offload: on,所以看起来它真的是NIC重新组装碎片。但我想知道为什么 sendto 的行为与 recvfrom 不同。如果数据包可以自动重组,为什么不分片?

旁注:应用程序需要发送这些数据包。使用 iptables 等设置转发将不起作用。

您的网卡可能启用了分段卸载,这意味着硬件可以在到达 OS 或您的代码之前重新 assemble TCP 分段。

您可以通过运行 ethtool -k检查是否是这种情况。 虽然透明地捕获 TCP 流量并以如此低的级别重新传输它通常比它值得的麻烦更多(通常最好在应用程序层执行此操作,终止 TCP 连接并为您的主机建立一个新的 TCP 连接B),如果你的网卡已经把数据包弄乱了,你就不能捕获并重新发送数据包。您需要:

  • 关闭通用分段卸载
  • 关闭通用接收卸载
  • 关闭 tcp-segmentation-offload
  • 如果您还要处理 UDP,请关闭 udp-fragmentation-offload
  • 如果您的数据包是 VLAN 封装的,请关闭 rx-vlan-offload/tx-vlan-offload
  • 可能会关闭 rx-checksumming 和 tx-checksumming。如果两者都有效,它要么有效 已启用,或者它已损坏。 RAW 套接字(如果启用),具体取决于您的 内核版本和网卡类型。

这些可以用 ethtool -K 命令转换 on/off,确切的语法在 ethtool 联机帮助页中有描述。