getifaddrs 返回 'bad file descriptor'/使应用程序崩溃

getifaddrs returning 'bad file descriptor'/crashing the application

在我的程序中,我有一个线程必须连续监视网络接口,因此它在 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() returns -1 和 errNo = EBADF(bad file descriptor)。为了不退出我的线程,我暂时用 continue 替换了 exit(因为我不希望我的程序因此而结束)。但是,我很想知道在哪些情况下会出现 getifaddrs() return 'bad file descriptor' 错误,以及我是否可以做些什么来避免这种情况发生?

编辑

用 '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 ()

Operating system: Red Hat Enterprise Linux Server release 7.8 (Maipo)

GLIBC version: 2.17

根据 man7.org getifaddrs,任何套接字操作都可能导致 EBADF

ERRORS

getifaddrs() may fail and set errno for any of the errors specified for socket(2), bind(2), getsockname(2), recvmsg(2), sendto(2), malloc(3), or realloc(3).


不相关,但是你freeifaddrs()在什么地方做过吗?

手册页中的以下示例已修改为包含您的忙循环 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 没有断言 __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.

终止您的进程

就像 [@matthieu] 让我们假设你没有弄乱你的 Stack and/or 指针 ifaddr 在你的监控逻辑中,这仍然可能是内核和 glibc 之间的通信错误并且会需要进一步调查。 作为变通方法,您可能会暂时捕获中止信号,如 How to Handle SIGABRT signal?

中所述

编辑:当然,如果您是 EBADF 的特例,您仍然必须 freeifaddrs(ifaddr) 才能继续...

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

在link中说崩溃的原因是。 “对 netlink 套接字的 recvmsg 系统调用特别 在文件描述符竞争后容易获取不相关的数据 (其中描述符在一个 多线程进程,作为文件描述符的结果 其他地方的管理问题).".

所以我认为您需要要么不要使用单独的线程,要么在 netlink 函数周围使用一些锁定机制。

至少在主线程中监控网络接口时确认它是否仍然崩溃。

幸运的是,我能够追查到问题背后的根本原因。场景已经解释 in detail here.

所以基本上,我程序中的一个线程有这个 'double-close' 有时会导致问题的错误。