Как программно обнаружить изменение IP-адреса в Linux? - PullRequest
26 голосов
/ 24 февраля 2009

Есть ли способ обнаружить изменения IP-адреса на локальном компьютере в Linux программно с помощью C ++?

Ответы [ 9 ]

46 голосов
/ 01 марта 2010

вот, пожалуйста ... это делает это без опроса.

он только слушает 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) {
                        uint32_t ipaddr = htonl(*((uint32_t *)RTA_DATA(rth)));
                        char name[IFNAMSIZ];
                        if_indextoname(ifa->ifa_index, name);
                        printf("%s is now %d.%d.%d.%d\n",
                               name,
                               (ipaddr >> 24) & 0xff,
                               (ipaddr >> 16) & 0xff,
                               (ipaddr >> 8) & 0xff,
                               ipaddr & 0xff);
                    }
                    rth = RTA_NEXT(rth, rtl);
                }
            }
            nlh = NLMSG_NEXT(nlh, len);
        }
    }
    return 0;
}
6 голосов
/ 24 февраля 2009

В 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" на интерфейс, который вы просматриваете. Все, что вам теперь нужно сделать, это опросить изменения.

4 голосов
/ 24 февраля 2009

Это нелегко никак. Каждый дистрибутив Linux использует разные места для хранения IP-адресов и т. Д. (Больше вариантов, если вы рассматриваете другие варианты UNIX). Вы можете использовать, например, /sbin/ifconfig для получения IP-адресов интерфейсов, но вы даже не можете быть уверены, что найдете его в этом месте или вообще и т. Д.

Кроме того, если у вас есть этот исполняемый файл, вы должны настроить поток, вызывающий его, для получения данных с заданным периодом (скажем, 5 секунд) и интерпретировать выходные данные. Например, это может отличаться, если у вас есть мосты и т. Д. И т. Д. То есть это не легко.

Решение, которое приходит мне в голову, заключается в том, что если у вас есть возможность использовать GNOME или какой-либо другой распространенный дистрибутив в качестве KDE, вы можете положиться на сообщения / информацию, которые они предоставляют. Например, NetworkManager выводит сигнал на стандартную шину DBUS при смене устройства. Вы должны реализовать слушатель для этих сигналов. Информация здесь (сейчас не работает, так что здесь кеш ). Обратите внимание на различные сообщения при добавлении нового интерфейса или при изменении одного из них IP-адреса. Это лучший способ, о котором я могу думать прямо сейчас.

2 голосов
/ 17 ноября 2009
Предложение

ste использовать ioctl SIOCGIFADDR было технически правильным, к сожалению, оно ненадежно для современных систем Linux, где один интерфейс может иметь несколько адресов без использования подинтерфейсов (например, eth0: 1), как это было сделано с теперь- устаревший ifconfig.

Лучше всего использовать getifaddrs (3), который присутствует в glibc 2.3: http://www.kernel.org/doc/man-pages/online/pages/man3/getifaddrs.3.html

К сожалению, это несколько неэффективно (вы возвращаете связанный список всех адресов на всех интерфейсах и вам придется перебирать, чтобы найти интересующие вас адреса), но в большинстве случаев вы, вероятно, не проверяете его больше, чем примерно раз в минуту, делая накладные расходы терпимыми.

2 голосов
/ 24 февраля 2009

Если установлен iproute2 и вы используете ядро ​​2.6,

/sbin/ip monitor

Выводит изменения в статусе локального интерфейса и адресах на стандартный вывод. Ваша программа может прочитать это.

Вы также можете использовать тот же механизм низкого уровня, что и инструмент iproute2 (я думаю, что это сокет netlink).

2 голосов
/ 24 февраля 2009

Если ваши пользователи используют NetworkManager, вы можете опросить NetworkManager.Connection.Active и NetworkManager.IP4Config через D-Bus, чтобы получить более перекрестный способ определения этой информации.

1 голос
/ 24 февраля 2009

Одним из способов было бы написать задание cron, содержащее вызов одной из библиотечных функций gethost. Если вы используете gethostbyname (), вы можете сравнить возвращаемые значения h_addr_list. Смотри человек gethostbyname.

Если вы хотите сделать это из вашей программы, создайте pthread, который делает то же самое, а затем спит в течение некоторого произвольного периода времени.

0 голосов
/ 30 мая 2018

Завершите протестированный пример на 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;
}
0 голосов
/ 17 ноября 2009

С справочной страницы rtnetlink:

ОПИСАНИЕ

Rtnetlink позволяет считывать и изменять таблицы маршрутизации ядра. Он используется в ядре для связи между различными подсистемами, хотя это использование здесь не задокументировано, а также для связи с программами пользовательского пространства. Сетевые маршруты, IP-адреса, параметры канала, настройки соседей, дисциплины очередей, классы трафика и классификаторы пакетов могут контролироваться через сокеты NETLINK_ROUTE. Он основан на сообщениях netlink, см. Netlink (7) для получения дополнительной информации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...