getifaddrs返回“bad file descriptor”/导致应用程序崩溃

3
在我的程序中,我有一个线程需要持续监控网络接口,因此它会在while循环中连续使用getifaddrs()函数。
    while(true) {
    
        struct ifaddrs *ifaddr, *ifa;
        if (getifaddrs(&ifaddr) == -1) {
            perror("getifaddrs couldn't fetch required data");
            exit(EXIT_FAILURE);
        }
  
        //Iterate through interfaces linked list
        for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        //monitoring logic
        }

       //Free linked list
       freeifaddrs(ifaddr);

       //Sleep for specified time fo next polling cycle
       usleep(1000);
    
    }

我的程序大部分时间都正常工作。然而,有时候getifaddrs()会返回-1和errNo=EBADF(坏文件描述符)。为了不退出我的线程,我暂时用continue替换了exit(因为我不想因此结束我的程序)。但是,我很想知道在哪些情况下getifaddrs()会返回“坏文件描述符”错误,并且我是否可以做一些事情来避免这种情况发生?

编辑

用'continue'替换'exit'并没有解决我的问题。有时对getifaddrs()的调用会导致应用程序崩溃!

下面是使用生成的核心文件从gdb获取的回溯信息。

Program terminated with signal 6, Aborted.
#0  0x00007fe2df1ef387 in raise () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.x86_64 keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.15.1-37.el7_6.x86_64 libcom_err-1.42.9-16.el7.x86_64 libgcc-4.8.5-39.el7.x86_64 libselinux-2.5-14.1.el7.x86_64 libstdc++-4.8.5-39.el7.x86_64 openssl-libs-1.0.2k-19.el7.x86_64 pcre-8.32-17.el7.x86_64 zlib-1.2.7-18.el7.x86_64
(gdb) bt
#0  0x00007fe2df1ef387 in raise () from /lib64/libc.so.6
#1  0x00007fe2df1f0a78 in abort () from /lib64/libc.so.6
#2  0x00007fe2df231ed7 in __libc_message () from /lib64/libc.so.6
#3  0x00007fe2df231fbe in __libc_fatal () from /lib64/libc.so.6
#4  0x00007fe2df2df4c2 in __netlink_assert_response () from /lib64/libc.so.6
#5  0x00007fe2df2dc412 in __netlink_request () from /lib64/libc.so.6
#6  0x00007fe2df2dc5ef in getifaddrs_internal () from /lib64/libc.so.6
#7  0x00007fe2df2dd310 in getifaddrs () from /lib64/libc.so.6
#8  0x000000000047c03c in __interceptor_getifaddrs.part.0 ()

操作系统: Red Hat Enterprise Linux Server 7.8版本(Maipo) GLIBC 版本: 2.17

3
引用https://man7.org/linux/man-pages/man3/getifaddrs.3.html中的说明:*getifaddrs()可能因为socket(2)、bind(2)、getsockname(2)、recvmsg(2)、sendto(2)、malloc(3)或realloc(3)等指定的任何错误而失败并设置errno。*其中一些函数将`EBADF`作为可能的`errno`值。您可以尝试使用系统调用跟踪(`strace`)来重现错误。这应该会显示哪个系统调用失败,并有助于分析问题的原因。 - Bodo
还是你在“监视逻辑”中弄错了 ifaddr 的内容? - Matthieu
请查看此链接,它可能会帮助您确定崩溃的原因。https://patchwork.ozlabs.org/project/netdev/patch/5638B93F.3090202@redhat.com/ - idris
1
那么...你设置了一个赏金来“吸引更多关注这个问题”,然后就忽略了所有的答案和帮助尝试?@RainerKeller提供了一个非常有趣的解决方案,我很想知道它是否对你有所帮助。 - Matthieu
毫无疑问,@RainerKeller的回答推动了我们的调查进展...但他自己提到他并没有真正回答核心问题...这就是为什么我还没有奖励赏金,希望会有更多的回应。 - Vishal Sharma
4个回答

3
以下示例来自手册页,已修改以包含您的忙循环和使用usleep。在我的服务器上,即使在没有任何网络接口失败或启用的情况下,该示例也可以裸奔并在valgrind下运行数分钟而不会抛出错误。我在CentOS 7.9上进行了测试,该系统具有glibc-2.17-323.el7_9.x86_64
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    struct ifaddrs *ifaddr, *ifa;
    int family, s;
    char host[NI_MAXHOST];

    while (1) {
        if (getifaddrs(&ifaddr) == -1) {
            perror("getifaddrs");
            exit(EXIT_FAILURE);
        }

        /* Walk through linked list, maintaining head pointer so we
          can free list later */

        for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
            if (ifa->ifa_addr == NULL)
                continue;
            family = ifa->ifa_addr->sa_family;
            /* Display interface name and family (including symbolic
               form of the latter for the common families) */
            // Commented out
        }
        freeifaddrs(ifaddr);
        usleep(1000);
    }
    exit(EXIT_SUCCESS);
}

有趣的是:GNU的glibc-2.17没有包含assert __netlink_assert_response,但GNU的glibc-2.31有。 所以,这是RedHat后来修补的内容(您可以使用我的步骤重新访问):
SRC=`basename $(rpm -q glibc) .x86_64`.src.rpm
wget --no-check-certificate http://vault.centos.org/7.9.2009/updates/Source/SPackages/${SRC}
CPIO=`basename ${SRC} .rpm`.cpio
rpm2cpio ${SRC} > ${CPIO}
mkdir glibc-src && cd glibc-src
cpio -ivd < ${CPIO}

这表明,在您的情况下失败的断言是由补丁glibc-rh1443872.patch添加的,该补丁说明如下:

commit 2eecc8afd02d8c65cf098cbae4de87f332dc21bd

Author: ...

Date: Mon Nov 9 12:48:41 2015 +0100

Terminate process on invalid netlink response from kernel [BZ #12926]

Bugzilla条目https://sourceware.org/bugzilla/show_bug.cgi?id=12926详细介绍了NetLink接口的丢失。
现在所有这些都不能回答您的问题:为什么getifaddrs失败并且glibc使用信号SIGABRT终止您的进程。

假设您在监控逻辑中没有弄乱堆栈和/或指针ifaddr,但仍可能存在内核和glibc之间的通信错误,需要进一步调查。作为解决方法,您可以暂时捕获中止信号,如如何处理SIGABRT信号?所述。

编辑:当然,如果您特别处理EBADF,在继续之前仍必须freeifaddrs(ifaddr)...


1

https://patchwork.ozlabs.org/project/netdev/patch/5638B93F.3090202@redhat.com/

在链接中,它说崩溃的原因是:“针对 netlink 套接字的 recvmsg 系统调用特别容易在文件描述符争用(其中描述符在多线程进程中同时关闭并重新打开,作为其他地方文件描述符管理问题的结果)后捕获无关数据。”。因此,我认为您需要要么不使用单独的线程,要么在 netlink 函数周围使用一些锁定机制。至少请确认当您在主线程中监视网络接口时是否仍会崩溃。

当我们在一个样例单线程程序中运行这个模块(接口监视器)时,我们无法重现这个问题...然而,在我们的多线程应用程序中,只有一个线程调用了getifaddrs()函数...在其他线程中没有进行任何getifaddrs或任何netlink函数的调用(我会再次检查)...此外,我已经使用地址和线程检查器运行了该应用程序,目前还没有发现相关问题。 - Vishal Sharma
所以我们可以确认崩溃发生在多线程环境下。我错了吗?@VishalSharma - idris
是的,这就是目前的观察结果。 - Vishal Sharma
1
你可以考虑在一个独立的进程中进行监控,并使用IPC与你的主应用程序进行通信,怎么样? - idris
是的...我想那可能是更好的解决方案。 - Vishal Sharma

0

幸运的是,我已经能够追踪到问题的根本原因。该情况已在此处详细说明

基本上,我的程序中的一个线程存在“双重关闭”错误,有时会导致问题。


坦白地说,一开始提到线程会更有帮助... - Rainer Keller
1
@RainerKeller 我很天真地跳过了那部分。我会尽力避免在将来重复这个错误。 - Vishal Sharma

0
根据man7.org getifaddrs,任何套接字操作都可能导致EBADF错误。
错误:
对于socket(2),bind(2),getsockname(2),recvmsg(2),sendto(2),malloc(3)或realloc(3)指定的任何错误,getifaddrs()可能会失败并设置errno。

不相关,但你在某个地方使用了freeifaddrs()吗?


是的,我确实使用freeifaddrs()。我已经编辑了问题中的代码。 - Vishal Sharma
2
在我看来,这并没有从用户或应用程序员的角度回答问题:“在哪些情况下getifaddrs()可能会返回'bad file descriptor'错误,以及我是否可以做些什么来避免这种情况发生”。可能设置此“errno”值的函数调用列表并没有真正解释错误可能发生的情况。 - Bodo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接