为什么 SIOCSIFMTU 设置 MTU 后仍无法发送大 Ethernet frame?
结论
从给出的完整函数看,问题出在重复使用 struct ifreq。SIOCGIFINDEX 会把接口索引写入与 ifr_name 共用的联合体字段,覆盖原先保存的接口名。之后调用 SIOCSIFMTU 时,内核根据 ifr_name 查找接口,但此时结构中的名称已经不是原来的 device。
ip link set core2red mtu 9000 直接使用接口名称设置 MTU,因此不会遇到这处结构复用问题。调用 SIOCSIFMTU 前,应重新填写 ifr_name,也可以为两次 ioctl 分别使用独立的 struct ifreq。
修正 struct ifreq 的使用
struct ifreq 中的接口名称、接口索引和 MTU 等成员共享存储空间。查询索引后,ifr_name 可能已被覆盖。设置 MTU 前可以重新复制设备名:
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, device, IFNAMSIZ - 1);
ifr.ifr_mtu = mtu;
if (ioctl(rawsock, SIOCSIFMTU, &ifr) < 0) {
perror("SIOCSIFMTU");
return -1;
}
也可以为查询索引和设置 MTU 分别准备结构体,避免后续操作覆盖联合体字段:
struct ifreq index_req = { 0 };
strncpy(index_req.ifr_name, device, IFNAMSIZ - 1);
if (ioctl(rawsock, SIOCGIFINDEX, &index_req) < 0) {
perror("SIOCGIFINDEX");
return -1;
}
sll.sll_family = AF_PACKET;
sll.sll_ifindex = index_req.ifr_ifindex;
sll.sll_protocol = htons(protocol);
struct ifreq mtu_req = { 0 };
strncpy(mtu_req.ifr_name, device, IFNAMSIZ - 1);
mtu_req.ifr_mtu = mtu;
if (ioctl(rawsock, SIOCSIFMTU, &mtu_req) < 0) {
perror("SIOCSIFMTU");
return -1;
}
检查 ioctl 的实际返回值和 errno。如果 SIOCSIFMTU 返回成功,但发送仍然失败,可以用 SIOCGIFMTU 或 ip link show 确认修改的是目标接口,并确认设置和发送操作处于同一个 network namespace。修改接口 MTU 通常需要进程具备相应的网络管理权限。
Socket 缓冲区与 MTU 的区别
SO_SNDBUF 和 SO_RCVBUF 用于设置 socket 缓冲区大小,不会修改网络接口 MTU,也不能替代正确的 MTU 设置。代码忽略了这两次 setsockopt 的返回值。排查缓冲区设置时,应检查返回值并读取 errno。
错误码 105 或 90 无法单独说明故障发生在哪个调用上。应记录失败的系统调用及其 errno,再判断原因是否与接口 MTU、socket 缓冲区或帧长限制有关。
检查帧长
MTU 通常表示接口可承载的三层数据包大小,不等于包含 Ethernet header 的完整帧长。发送大于 1500 字节的 Ethernet frame 时,应确认目标接口、链路及相关设备支持所需的帧长。帧中如果还包含 VLAN header 等字段,也需要把这些额外字节计算在内。
备注:内容仅供参考。
