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

时间:2017-03-27 16:37:46

标签: c linux sockets networking ethernet

问题:在原始套接字上,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确认如图所示,因此必须在较低级别进行一些重组,如网络控制器本身。 Received packet

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

编辑:我刚刚检查了ethtool -k interf的机器,并且所有机器都获得了tcp-segmentation-offload: on,所以看起来它真的是网卡重组片段。但我想知道为什么sendto不会表现为recvfrom。如果数据包可以自动重新组装,为什么不分段?

附注:应用程序需要发送这些数据包。使用iptables等设置转发工作无效。

1 个答案:

答案 0 :(得分:2)

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

您可以通过运行ethtool -k来检查是否是这种情况。 虽然透明地捕获TCP流量并在如此低的水平上重新传输它通常比它的价值更麻烦(一个通常更好地在应用层执行此操作,终止TCP连接并建立到主机的新TCP连接B),如果您的网卡与数据包混乱,则无法捕获和重新发送数据包。你需要:

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

可以使用ethtool -K命令打开/关闭它们,ethtool联机帮助页中描述了确切的语法。