【问题标题】:How to detect IP address change programmatically in Linux?如何在 Linux 中以编程方式检测 IP 地址更改?
【发布时间】:2009-02-23 23:03:34
【问题描述】:

有没有办法在 Linux 中使用 C++ 以编程方式检测本地机器上的 IP 地址更改?

【问题讨论】:

  • +1 :常见问题,很高兴在这里看到它。
  • 丹尼,恕我直言,如果您的解决方案不是轮询,那是什么?您显然在等待从套接字接收特定消息,当您可以简单地轮询 ifconfig 的输出或按照其他人的建议使用 getifaddrs 时,这也很复杂

标签: c++ linux


【解决方案1】:

给你..这不需要轮询。

它只侦听 RTM_NEWADDR,但如果您需要,它应该很容易更改为支持 RTM_DELADDR

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

int
main()
{
    struct sockaddr_nl addr;
    int sock, len;
    char buffer[4096];
    struct nlmsghdr *nlh;

    if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
        perror("couldn't open NETLINK_ROUTE socket");
        return 1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_IPV4_IFADDR;

    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("couldn't bind");
        return 1;
    }

    nlh = (struct nlmsghdr *)buffer;
    while ((len = recv(sock, nlh, 4096, 0)) > 0) {
        while ((NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE)) {
            if (nlh->nlmsg_type == RTM_NEWADDR) {
                struct ifaddrmsg *ifa = (struct ifaddrmsg *) NLMSG_DATA(nlh);
                struct rtattr *rth = IFA_RTA(ifa);
                int rtl = IFA_PAYLOAD(nlh);

                while (rtl && RTA_OK(rth, rtl)) {
                    if (rth->rta_type == IFA_LOCAL) {
                        char name[IFNAMSIZ];
                        if_indextoname(ifa->ifa_index, name);
                        char ip[INET_ADDRSTRLEN];
                        inet_ntop(AF_INET, RTA_DATA(rth), ip, sizeof(ip));
                        printf("interface %s ip: %s\n", name, ip);
                    }
                    rth = RTA_NEXT(rth, rtl);
                }
            }
            nlh = NLMSG_NEXT(nlh, len);
        }
    }
    return 0;
}

【讨论】:

  • 很棒的代码。有没有办法只观察特定界面的变化?
  • @watain struct ifaddrmsg 有一个成员ifa_index,这是地址关联的接口的接口索引。
  • 应该在这些 netlink API 的手册页中注明,建议不要使用这个低级接口,而是使用 libnetlink lib ... 这反过来列出了这个库的使用作为一个错误并指出使用libmnl 代替
  • 由于使用的是Netlink协议,这里有一个文档描述它是什么inai.de/documents/Netlink_Protocol.pdf
  • @Vencat,外部 while() 不考虑轮询,因为 recv() 阻塞,直到有要处理的事件。内部 2 while()s 只是在迭代。
【解决方案2】:

在 C 中,获取我使用的当前 IP:

    int s;
    struct ifreq ifr = {};

    s = socket(PF_INET, SOCK_DGRAM, 0);

    strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));

    if (ioctl(s, SIOCGIFADDR, &ifr) >= 0)
        printf("%s\n",
          inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));

将“eth0”替换为您正在查看的界面。您现在需要做的就是轮询更改。

【讨论】:

  • 这正是“ifconfig”获取接口当前IP地址的方式。要找出存在哪些接口名称,它实际上会打开并解析“/proc/net/dev”。应该有一种方法可以在 IP 更改时从内核获取事件。我的猜测是使用 uevents。
  • 遗憾的是,这对于现代系统来说并不可靠,在现代系统中,一个接口可能有多个地址而不使用子接口。
【解决方案3】:

无论如何这都不容易。每个 linux 发行版使用不同的位置来存储 IP 地址等(如果您考虑其他 UNIX 变体,则会有更多变化)。您可以使用,例如,/sbin/ifconfig 来获取接口的 IP 地址,但您甚至无法确定是否会在这个地方找到它,或者根本找不到,等等。 p>

另外,如果你有那个可执行文件,你必须设置一个线程调用它来获取给定时间段(比如 5 秒)的数据,并解释输出。它可能会有所不同,例如,如果您有桥梁等。也就是说,这并不容易。

我想到的一个解决方案是,如果您有机会使用 GNOME 或其他一些广泛的发行版作为 KDE,您可以依赖它们提供的消息/信息。例如,NetworkManager 在设备发生变化时向DBUS standard bus 输出信号。您必须为这些信号实现一个​​侦听器。信息here(现在不工作,所以这里是cache)。请注意添加新接口或其中一个接口更改 IP 地址时的不同消息。这是我目前能想到的最好方法。

【讨论】:

    【解决方案4】:

    如果您的用户使用 NetworkManager,您可以通过 D-Bus 轮询 NetworkManager.Connection.Active 和 NetworkManager.IP4Config 以获得确定此信息的更多交叉分发方式。

    【讨论】:

      【解决方案5】:

      如果安装了 iproute2 并且您使用的是 2.6 内核,

      /sbin/ip monitor
      

      将本地接口状态和地址的变化输出到标准输出。你的程序可以读取这个。

      您也可以使用与 iproute2 工具相同的低级机制(我认为它是一个 netlink 套接字)。

      【讨论】:

        【解决方案6】:

        ste 建议使用 ioctl SIOCGIFADDR 过去在技术上是正确的,不幸的是它对于现代 Linux 系统是不可靠的,在现代 Linux 系统中,单个接口可以有多个地址而不使用子接口(例如 eth0:1),就像现在所做的那样 -过时的 ifconfig。

        最好的办法是使用 glibc 2.3 中提供的 getifaddrs(3):http://www.kernel.org/doc/man-pages/online/pages/man3/getifaddrs.3.html

        不幸的是,它的效率有点低(您会返回所有接口上所有地址的链接列表,并且必须遍历以找到您感兴趣的地址),但在大多数情况下,您可能不会检查它超过每分钟左右一次,使开销可以忍受。

        【讨论】:

          【解决方案7】:

          一种方法是编写一个 cron 作业,其中包含对 gethost 系列库函数的调用。如果您使用 gethostbyname(),您可以比较 h_addr_list 的返回值。参见 man gethostbyname。

          如果您想在程序中执行此操作,请生成一个执行相同操作的 pthread,然后休眠一段时间。

          【讨论】:

            【解决方案8】:

            用 C 语言完成测试示例,并在单独的线程中监视通知:

            #include <sys/socket.h> // AF_INET, socket(), bind()
            #include <ifaddrs.h> // struct ifaddrs, getifaddrs()
            #include <netinet/in.h> // struct sockaddr_in
            #include <arpa/inet.h> // inet_ntoa(), htonl()
            #include <net/if.h> // if_indextoname()
            #include <pthread.h>
            #include <asm/types.h>
            #include <linux/netlink.h>
            #include <linux/rtnetlink.h>
            #include <stdbool.h>
            
            typedef enum {
                IP_ADDR_ADD,
                IP_ADDR_REMOVE
            } ip_address_change_notification_type_t;
            
            typedef void (*ip_address_change_notification_callback_t)(ip_address_change_notification_type_t type, uint32_t ipaddr, void *userdata);
            
            static int ip_address_change_notification_socket = -1;
            static pthread_t ip_address_change_notification_thread;
            static ip_address_change_notification_callback_t ip_address_change_notification_callback;
            static void *ip_address_change_notification_callback_userdata;
            
            void *ip_address_change_notification_worker(void *arg)
            {
                fprintf(stderr, "ip_address_change_notification_worker entered.\n");
                if (ip_address_change_notification_socket == -1) {
                    goto done;
                }
            
                char buffer[4096];
                struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
                int len;
                while ((len = recv(ip_address_change_notification_socket, nlh, sizeof(buffer), 0)) > 0) {
                    for (; (NLMSG_OK(nlh, len)) && (nlh->nlmsg_type != NLMSG_DONE); nlh = NLMSG_NEXT(nlh, len)) {
                        if (nlh->nlmsg_type == RTM_NEWADDR) {
                            fprintf(stderr, "Netlink: RTM_NEWADDR\n");
                        } else if (nlh->nlmsg_type == RTM_DELADDR) {
                            fprintf(stderr, "Netlink: RTM_DELADDR\n");
                        } else {
                            fprintf(stderr, "Netlink: nlmsg_type=%d\n", nlh->nlmsg_type);
                            continue; // Some other kind of announcement.
                        }
            
                        struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
                        struct rtattr *rth = IFA_RTA(ifa);
                        int rtl = IFA_PAYLOAD(nlh);
                        for (; rtl && RTA_OK(rth, rtl); rth = RTA_NEXT(rth,rtl)) {
                            char name[IFNAMSIZ];
                            uint32_t ipaddr;
            
                            if (rth->rta_type != IFA_LOCAL) continue;
                            ipaddr = *((uint32_t *)RTA_DATA(rth)); // In network byte-order.
                            fprintf(stderr, "Interface %s %s has IP address %s\n", if_indextoname(ifa->ifa_index, name), (nlh->nlmsg_type == RTM_NEWADDR ? "now" : "no longer"), inet_ntoa(*((struct in_addr *)&ipaddr)));
                            if (ip_address_change_notification_callback) (*ip_address_change_notification_callback)((nlh->nlmsg_type == RTM_NEWADDR ? IP_ADDR_ADD : IP_ADDR_REMOVE), ipaddr, ip_address_change_notification_callback_userdata);
                        }
                    }
                }
            
            done:
                fprintf(stderr, "ip_address_change_notification_worker exited.\n");
                return (NULL);
            }
            
            bool begin_ip_address_change_notifications(ip_address_change_notification_callback_t callback, void *userdata)
            {
                if (ip_address_change_notification_socket != -1) return false;
            
                ip_address_change_notification_callback = callback;
                ip_address_change_notification_callback_userdata = userdata;
            
                if ((ip_address_change_notification_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
                    perror("begin_ip_address_change_notifications socket");
                    return false;
                }
            
                struct sockaddr_nl addr;
                memset(&addr, 0, sizeof(addr));
                addr.nl_family = AF_NETLINK;
                addr.nl_groups = RTMGRP_IPV4_IFADDR;
                if (bind(ip_address_change_notification_socket, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
                    perror("begin_ip_address_change_notifications bind");
                    goto bail;
                }
            
                pthread_attr_t attr;
                pthread_attr_init(&attr);
                pthread_attr_setdetachstate(&attr, 1); // Preclude the need to do pthread_join on the thread after it exits.
                int err = pthread_create(&ip_address_change_notification_thread, &attr, ip_address_change_notification_worker, NULL);
                pthread_attr_destroy(&attr);
                if (err != 0) {
                    fprintf(stderr, "Error creating ip address change notification thread.\n");
                    goto bail;
                }
            
                return (true);
            
            bail:
                close(ip_address_change_notification_socket);
                ip_address_change_notification_socket = -1;
            
                ip_address_change_notification_callback = NULL;
                ip_address_change_notification_callback_userdata = NULL;
                return false;
            }
            
            void end_ip_address_change_notifications(void)
            {
                if (ip_address_change_notification_socket == -1) return;
            
                pthread_cancel(ip_address_change_notification_thread);
            
                close(ip_address_change_notification_socket);
                ip_address_change_notification_socket = -1;
            
                ip_address_change_notification_callback = NULL;
                ip_address_change_notification_callback_userdata = NULL;
            }
            

            【讨论】:

              【解决方案9】:

              来自 rtnetlink 的手册页:

              描述

              Rtnetlink 允许读取和更改内核的路由表。它在内核中用于在各种子系统之间进行通信,尽管这里没有记录这种用法,也用于与用户空间程序进行通信。网络路由、IP 地址、链接参数、邻居设置、排队规则、流量类别和数据包分类器都可以通过 NETLINK_ROUTE 套接字进行控制。它基于 netlink 消息,有关详细信息,请参阅 netlink(7)。

              【讨论】:

                【解决方案10】:

                使用 libnl-3 库,检测链接和 ip4 地址变化。

                参考 - https://www.infradead.org/~tgr/libnl/doc/core.html#_introduction

                #include <netlink/netlink.h>
                #include <netlink/socket.h>
                #include <netlink/msg.h>
                #include <arpa/inet.h>
                #include <iostream>
                
                static char ip4Addr[INET_ADDRSTRLEN];
                
                static int parseAddress(struct nlmsghdr *hdr)
                {
                    std::cout << "parseAddress" << std::endl;
                
                    struct ifaddrmsg *iface = (struct ifaddrmsg *)nlmsg_data(hdr);
                
                    struct nlattr *attrs[IFA_MAX + 1];
                
                    if (nlmsg_parse(hdr, sizeof(struct ifaddrmsg), attrs, IFA_MAX, nullptr) < 0)
                    {
                        std::cerr << "problem parsing Netlink response" << std::endl;
                        return -1;
                    }
                
                    if (attrs[IFA_ADDRESS] == nullptr)
                    {
                        std::cerr << "Address Never Received "
                                  << std::endl;
                        return -1;
                    }
                
                    inet_ntop(iface->ifa_family, nla_data(attrs[IFA_ADDRESS]), ip4Addr, sizeof(ip4Addr));
                
                    if ((hdr->nlmsg_type == RTM_NEWADDR) && (iface->ifa_family == AF_INET))
                    {
                        std::cout << "IPv4 Address added : " << ip4Addr << std::endl;
                    }
                
                    if ((hdr->nlmsg_type == RTM_DELADDR) && (iface->ifa_family == AF_INET))
                    {
                        std::cout << "IPv4 Address deleted : " << ip4Addr << std::endl;
                    }
                    return 0;
                }
                
                static int parseLink(struct nlmsghdr *hdr)
                {
                    std::cout << "parseLink" << std::endl;
                    struct ifinfomsg *iface = (struct ifinfomsg *)nlmsg_data(hdr);
                
                    struct nlattr *attrs[IFLA_MAX + 1];
                
                    if (nlmsg_parse(hdr, sizeof(struct ifinfomsg), attrs, IFLA_MAX, nullptr) < 0)
                    {
                        std::cerr << "problem parsing Netlink response" << std::endl;
                        return -1;
                    }
                
                    if (attrs[IFLA_IFNAME] != nullptr)
                    {
                        if (hdr->nlmsg_type == RTM_NEWLINK)
                        {
                            std::cout << (char *)nla_data(attrs[IFLA_IFNAME]) << std::endl;
                        }
                        else if (hdr->nlmsg_type == RTM_DELLINK)
                        {
                            std::cout << (char *)nla_data(attrs[IFLA_IFNAME]) << std::endl;
                        }
                    }
                    return 0;
                }
                
                static int receiveNewMsg(struct nl_msg *msg, void *arg)
                {
                    struct nlmsghdr *nlh = nlmsg_hdr(msg);
                    int len = nlh->nlmsg_len;
                    int type = nlh->nlmsg_type;
                    while (nlmsg_ok(nlh, len))
                    {
                        if (type != RTM_NEWLINK && type != RTM_DELLINK && type != RTM_NEWADDR && type != RTM_DELADDR)
                        {
                            if (nlh->nlmsg_type == NLMSG_DONE)
                            {
                                std::cout << "message complete" << std::endl;
                            }
                            nlh = nlmsg_next(nlh, &len);
                            continue;
                        }
                        if ((nlh->nlmsg_type == RTM_NEWLINK) || (nlh->nlmsg_type == RTM_DELLINK))
                        {
                            parseLink(nlh);
                        }
                        if ((nlh->nlmsg_type == RTM_NEWADDR) || (nlh->nlmsg_type == RTM_DELADDR))
                        {
                            parseAddress(nlh);
                        }
                        nlh = nlmsg_next(nlh, &len);
                    }
                    return 1;
                }
                
                int main(int argc, char const *argv[])
                {
                    struct nl_sock *sk;
                
                    /* Allocate a new socket */
                    sk = nl_socket_alloc();
                
                    /*
                    * Notifications do not use sequence numbers, disable sequence number checking.
                    */
                    nl_socket_disable_seq_check(sk);
                
                    /*
                    * Define a callback function, which will be called for each notification received
                    */
                    nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, receiveNewMsg, nullptr);
                    nl_socket_modify_cb(sk, NL_CB_FINISH, NL_CB_CUSTOM, receiveNewMsg, nullptr);
                
                    /* Connect to routing netlink protocol */
                    nl_connect(sk, NETLINK_ROUTE);
                
                    /* Subscribe to link notifications group */
                    nl_socket_add_memberships(sk, RTNLGRP_LINK, 0);
                    nl_socket_add_memberships(sk, RTNLGRP_IPV4_IFADDR, 0);
                
                    /*
                    * Start receiving messages. The function nl_recvmsgs_default() will block
                    * until one or more netlink messages (notification) are received which
                    * will be passed on to my_func().
                    */
                
                    while (1)
                        nl_recvmsgs_default(sk);
                
                    return 0;
                }
                

                【讨论】:

                  猜你喜欢
                  • 2019-07-10
                  • 2012-07-16
                  • 2023-03-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-07-07
                  • 1970-01-01
                  • 2013-08-20
                  • 2023-03-27
                  相关资源
                  最近更新 更多