使用 ioctl 修改 IP 地址时的不良副作用

Posted

技术标签:

【中文标题】使用 ioctl 修改 IP 地址时的不良副作用【英文标题】:Unwanted side-effects when modifying IP address with ioctl 【发布时间】:2017-04-17 21:15:05 【问题描述】:

问题:

有没有办法使用ioctl 改变只需要的接口组件而不影响网络接口的其他部分?

推理

我正在编写一个 C++ 程序,它允许用户在 Linux 机器上独立地更改 IP 地址、广播地址、网络掩码和默认网关。我为 IP、Bcast 和 NMask 解决方案修改了this code。但是,使用ioctl 更改 IP 地址会自动修改我的广播/网络掩码,并清除内核 IP 路由表。

这是一个例子。在运行下面的代码之前,这是ifconfigroute -n的结果:

这是修改IP地址的代码的功能版本:

#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <string.h>
#include <iostream>

int main(int argc, const char *argv[]) 
    struct ifreq ifr;
    struct sockaddr_in* addr = (struct sockaddr_in*)&ifr.ifr_addr;
    const char * name = "eth0";
    int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);

    strncpy(ifr.ifr_name, name, IFNAMSIZ);

    ifr.ifr_addr.sa_family = AF_INET;
    inet_pton(AF_INET, "10.10.2.59", &addr->sin_addr);
    if(ioctl(fd, SIOCSIFADDR, &ifr) < 0)
    
        std::cout << "Failed to set IP: " << strerror(errno) << std::endl;
        return -1;
    

    if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
    
        std::cout << "Failed to get flags: " << strerror(errno) << std::endl;
        return -2;
    
    strncpy(ifr.ifr_name, name, IFNAMSIZ);
    ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);

    if(ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
    
        std::cout << "Failed to set flags: " << strerror(errno) << std::endl;
        return -3;
    

    return 0;

这是运行上述程序后得到的网卡状态:

如您所见,IP 地址已根据需要进行了修改,但接口的其余部分已更改(这是不可取的)。

我在 Internet 或 netdevice man page 上找不到任何关于阻止 ioctl 自动修改网络接口的其他部分的信息。

我知道我可以设置 struct ifreq 的值,所以 BcastMask 组件不会改变,但我希望能够修改每个组件单独而不用担心其他人的价值。我特别不想跟踪默认网关,并且每次更改 IP 地址时都必须添加它。

更新

经过更多的测试和研究,我发现在运行系统命令ifconfigip时仍然会出现这个问题。例如,如果不是运行上面的代码,而是运行ifconfig eth0 10.10.2.59,结果是一样的。

使用ip 命令有些不同,因为更改IP 地址需要运行ip addr del 10.10.2.58/16 dev eth0 &amp;&amp; ip addr add 10.10.2.59/16 dev eth0。因此,您删除一个已知的地址/网络掩码组合并添加另一个。由于未指定广播地址,因此将其设置为0.0.0.0。但是,此命令仍会从路由表中删除默认网关。

【问题讨论】:

如果您同时使用ifconfig 等标准实用程序,是否会发生这种情况? 也许你应该看看他们的源代码,看看他们是怎么做的。 我已经对帖子进行了更新,看来标准实用程序实际上确实会导致同样的问题。 我怀疑您只是丢失了默认网关,因为它位于您要更改其配置的同一接口上。如果您有两个接口并更改了不同的接口,我希望保留默认 gw。 我怀疑这就是正在发生的事情,但我仍然不知道如何防止它被删除——如果它甚至可以防止的话。 【参考方案1】:

似乎(通过实验)ioctl 标志在它们如何影响网络接口的其余部分方面存在某种形式的优先排序:

SIOCSIFADDR 强制重置给定网络接口的网络掩码、广播地址和路由表条目 SIOCSFNETMASK 强制重置广播地址和路由表 SIOCSFBRDADDR仅强制重置路由表

这意味着你运行ioctl 调用的顺序很重要——首先你必须设置IP,然后是子网掩码,然后是广播地址,然后是任何路由。否则ioctl 会自动覆盖您之前所做的更改。

因此,我最终不得不跟踪在仅更改部分网络接口时受影响的所有子组件。

例如,如果要更改子网掩码,我首先读取旧的广播地址(带有 SIOCGIFBRDADDR 标志的 ioctl 调用)和默认网关(阅读时请参阅 here以编程方式)并存储它们。然后在用ioctl 更改子网掩码后,我重新分配了广播地址和默认网关(按此顺序),在用户看来,只有子网掩码被更改了。

这并不能完全回答最初的问题,但我找不到任何其他方法可以只修改网络接口的一个组件而不影响其他组件。如果有人找到更好的方法,我会很高兴知道。

【讨论】:

以上是关于使用 ioctl 修改 IP 地址时的不良副作用的主要内容,如果未能解决你的问题,请参考以下文章

使用 SIOCSIFADDR ioctl 设置 IP 地址

使用 ioctl 或 netlink 向接口添加和删除 IP 地址

Linux使用ioctl设置ip地址

2台路由器连接后副路由器会不会占用主路由器一个ip地址!

如何在 linux 中使用 ioctl 获取网关 ip 和名称服务器 ip

请教一个副路由器IP地址自动跳动的问题