【问题标题】:getifaddrs returning 'bad file descriptor'/crashing the applicationgetifaddrs 返回“错误的文件描述符”/使应用程序崩溃
【发布时间】:2022-01-11 12:08:53
【问题描述】:

在我的程序中,我有一个线程必须持续监控网络接口,因此它在 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(bad file descriptor)。为了不退出我的线程,我暂时用 continue 替换了 exit(因为我不希望我的程序因此而结束)。但是,我很想知道在哪些情况下 getifaddrs() 会返回“错误文件描述符”错误,以及我是否可以采取一些措施避免这种情况发生?

编辑

用“继续”替换“退出”并没有解决我的问题。有时调用 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 版(迈坡)

GLIBC 版本:2.17

【问题讨论】:

  • 引用手册页man7.org/linux/man-pages/man3/getifaddrs.3.htmlgetifaddrs() 可能会失败并为 socket(2)、bind(2)、getsockname(2)、recvmsg( 2)、sendto(2)、malloc(3) 或 realloc(3)。 其中一些函数将EBADF 指定为可能的errno 值。您可以尝试使用系统调用跟踪 (strace) 重现错误。这应该显示哪个系统调用失败并且可能有助于分析问题的原因。
  • 或者你在“监控逻辑”中搞砸了ifaddr 内容?
  • 查看此链接。它可以帮助您确定崩溃的原因。 patchwork.ozlabs.org/project/netdev/patch/…
  • 所以......你设置了一个赏金来“吸引更多关注这个问题”然后简单地忽略所有答案并尝试提供帮助? @RainerKeller 提供了一个非常有趣的解决方案,我很想知道它是否对您有所帮助。
  • 毫无疑问@RainerKeller 的回答推动了我们的调查......但他自己提到他并没有真正回答核心问题......这就是为什么我还没有奖励赏金,希望可能会有更多回应。

标签: c linux glibc rhel7


【解决方案1】:

手册页中的以下示例已修改为包含您的繁忙循环,其中usleep 在 valgrind 下运行了几分钟而没有抛出错误;尽管我的服务器在运行此示例时没有任何网络接口出现故障或上线。

我在具有glibc-2.17-323.el7_9.x86_64 的 CentOS 7.9 上进行了测试。

#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 添加的,其中指出:

提交 2eecc8afd02d8c65cf098cbae4de87f332dc21bd

作者:...

日期:2015 年 11 月 9 日星期一 12:48:41 +0100

在来自内核的无效网络链接响应中终止进程 [BZ #12926]

Bugzilla 条目 https://sourceware.org/bugzilla/show_bug.cgi?id=12926 提供了有关 NetLink 接口有损的详细信息。

现在所有这些都不能回答您的问题:为什么 getifaddrs 会失败并且 glibc 会使用信号 SIGABRT 杀死您的进程。

像 [@matthieu] 一样,假设您没有在监控逻辑中弄乱您的堆栈和/或指针 ifaddr,这仍然可能是内核和 glibc 之间的通信错误,需要进一步调查。 作为一种解决方法,您可能会暂时捕获中止信号,如How to Handle SIGABRT signal?中所述

编辑:当然,如果您是EBADF 的特殊情况,您仍然必须在继续之前freeifaddrs(ifaddr)...

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • 当我们在一个示例单线程程序中运行这个模块(接口监视器)时,我们无法重现这个问题......但是,即使在我们的多线程应用程序中,也有只有一个调用 getifaddrs() 的线程...在其他线程的其余部分中,没有对 getifaddrs 进行此类调用,甚至没有对任何 netlink 函数进行调用(AFAIR,将重新检查)...而且我已经运行了应用程序address sanitiser 和 thread sanitiser,到目前为止还没有发现相关问题。
    • 所以我们可以确认崩溃发生在多线程环境下。我错了吗? @VishalSharma
    • 是的..这就是迄今为止的观察结果。
    • 如何在单独的进程中监控并通过 IPC 与您的主应用程序通信?
    • 是的...我想这可能是一个更好的解决方案
    【解决方案3】:

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

    错误

    getifaddrs() 可能会失败并为任何错误设置 errno 为 socket(2)、bind(2)、getsockname(2)、recvmsg(2) 指定, sendto(2)、malloc(3) 或 realloc(3)。


    不相关,但你在某处做freeifaddrs() 吗?

    【讨论】:

    • 是的,我确实使用了 freeifaddrs()。我已经编辑了问题中的代码。
    • 恕我直言,这并不能从用户或应用程序程序员的角度回答问题“在哪些情况下 getifaddrs() 会返回“错误文件描述符”错误以及我是否可以这样做这不会发生”。可能设置此errno 值的函数调用列表并没有真正解释在哪种情况下可能会发生错误。
    猜你喜欢
    • 2014-11-23
    • 2013-09-17
    • 2020-10-27
    • 2019-06-11
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 2018-04-21
    • 1970-01-01
    相关资源
    最近更新 更多