简体   繁体   English

如何在 Linux 中以编程方式检测 IP 地址更改?

[英]How to detect IP address change programmatically in Linux?

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

here you go.. this does it without polling.给你.. 无需轮询即可完成。

it only listens for RTM_NEWADDR but it should be easy to change to support RTM_DELADDR if you need它只侦听 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;
}

In C, to get the current IP I use:在 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));

Replace "eth0" with the interface you're looking at.将“eth0”替换为您正在查看的界面。 All you now need to do is poll for a change.您现在需要做的就是轮询更改。

It is not easy in any way.无论如何都不容易。 Each linux distribution uses different places to store IP addresses, etc. (more variation if you consider other UNIX variants).每个 linux 发行版使用不同的地方来存储 IP 地址等(如果您考虑其他 UNIX 变体,则会有更多变化)。 You can use, for example, /sbin/ifconfig to obtain the IP addresses of the interfaces, but you cannot even be sure if you'll find it at this place, or at all, etc.例如,您可以使用/sbin/ifconfig来获取接口的 IP 地址,但您甚至无法确定是否会在此位置找到它,或者根本无法确定,等等。

Also, given you have that executable, you have to set up a thread calling it to obtain the data with a given period (say 5 seconds), and interpret the output.此外,如果您拥有该可执行文件,您必须设置一个线程来调用它以获取给定时间段(例如 5 秒)内的数据,并解释输出。 It may vary, for example, if you have bridges, etc. etc. That is, it is not easy.它可能会有所不同,例如,如果您有桥梁等。也就是说,这并不容易。

A solution that comes to my mind is, if you have the opportunity of using GNOME or some other widespread distribution as KDE, you can rely on the messages/informations they give.我想到的一个解决方案是,如果您有机会使用 GNOME 或其他一些广泛的发行版作为 KDE,您可以依赖它们提供的消息/信息。 For example, NetworkManager outputs a signal to the DBUS standard bus when a device changes.例如,当设备发生变化时, NetworkManager会向DBUS 标准总线输出信号。 You have to implement a listener for those signal.你必须为这些信号实现一个​​监听器。 Information here (not working right now, so here is a cache ).信息在这里(现在不工作,所以这里是一个缓存)。 Note the different messages when a new interface is added, or when one of them changes the IP address.请注意添加新接口或其中之一更改 IP 地址时的不同消息。 This is the best way I can think of right now.这是我目前能想到的最好的方法。

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

ste's suggestion to use ioctl SIOCGIFADDR used to be technically correct, unfortunately it is unreliable for modern Linux systems, where a single interface can have multiple addresses without using sub-interfaces (eg eth0:1) as was done with the now-obsolete ifconfig. ste 使用 ioctl SIOCGIFADDR 的建议在技术上是正确的,不幸的是它对于现代 Linux 系统是不可靠的,在现代 Linux 系统中,单个接口可以有多个地址,而无需使用子接口(例如 eth0:1),就像现在已经过时的 ifconfig 所做的那样。

Your best bet is to use getifaddrs(3), which is present from glibc 2.3: http://www.kernel.org/doc/man-pages/online/pages/man3/getifaddrs.3.html最好的办法是使用 getifaddrs(3),它来自 glibc 2.3: http ://www.kernel.org/doc/man-pages/online/pages/man3/getifaddrs.3.html

Unfortunately it's somewhat inefficient (you get back a linked list of all addresses on all interfaces and will have to iterate through to find the ones you're interested in), but in most cases you're probably not checking it more than once a minute or so, making the overhead tolerable.不幸的是,它有点低效(您会返回所有接口上所有地址的链接列表,并且必须遍历以找到您感兴趣的地址),但在大多数情况下,您可能不会每分钟检查一次以上左右,使开销可以忍受。

If iproute2 is installed and you're on a 2.6 kernel,如果安装了 iproute2 并且您使用的是 2.6 内核,

/sbin/ip monitor

Will output changes in local interface status and addresses to stdout.将本地接口状态和地址的变化输出到标准输出。 Your program can read this.您的程序可以读取此内容。

You could also use the same low level mechanism as the iproute2 tool does (I think it's a netlink socket).您也可以使用与 iproute2 工具相同的低级机制(我认为它是一个 netlink 套接字)。

Complete tested example in C with notifications watched for in separate thread:在 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;
}

One way would be to write a cron job which contains a call to one the gethost family of library functions.一种方法是编写一个 cron 作业,其中包含对 gethost 系列库函数的调用。 If you use gethostbyname() you can compare the return values of h_addr_list.如果您使用 gethostbyname(),您可以比较 h_addr_list 的返回值。 See man gethostbyname.请参阅 man gethostbyname。

If you're want to do this from within your program, spawn a pthread which does the same thing, then sleeps for some arbitrary period of time.如果您想在程序中执行此操作,请生成一个执行相同操作的 pthread,然后休眠一段时间。

From man page of rtnetlink:从 rtnetlink 的手册页:

DESCRIPTION描述

Rtnetlink allows the kernel's routing tables to be read and altered. Rtnetlink 允许读取和更改内核的路由表。 It is used within the kernel to communicate between various subsystems, though this usage is not documented here, and for communication with user-space programs.它在内核中用于在各种子系统之间进行通信,尽管这里没有记录这种用法,以及用于与用户空间程序的通信。 Network routes, ip addresses, link parameters, neighbor setups, queueing disciplines, traffic classes and packet classifiers may all be controlled through NETLINK_ROUTE sockets.网络路由、IP 地址、链接参数、邻居设置、排队规则、流量类别和数据包分类都可以通过 NETLINK_ROUTE 套接字进行控制。 It is based on netlink messages, see netlink(7) for more information.它基于 netlink 消息,有关详细信息,请参阅 netlink(7)。

Using the libnl-3 library, detect link and ip4 address change.使用 libnl-3 库,检测链接和 ip4 地址变化。

Reference - https://www.infradead.org/~tgr/libnl/doc/core.html#_introduction参考 - 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM