Как перечислить все IP-адреса, подключенные к машине, в POSIX C? - PullRequest
5 голосов
/ 21 июля 2009

Справочная информация:

Я пишу демон, который устанавливает исходящие соединения TCP / IP. Он будет работать на компьютерах с несколькими (не зацикленными) IP-адресами. Я бы хотел, чтобы пользователи могли указать в файле конфигурации демона, какой IP-адрес (а) использовать для исходящих соединений, или * для использования всех.

Адреса будут использоваться поочередно, каждое соединение выходит из IP-адреса, который использовался не так давно. Это поведение важно, так как * является заменой для «всех», поэтому демоны, работающие на нескольких машинах, могут указывать на один и тот же файл конфигурации на файловом ресурсе и каждый из них может использовать свой собственный набор IP-адресов.

Проблема:

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

Я на C, и, если возможно, я бы хотел использовать только POSIX, но демон, вероятно, будет работать только на Linux-блоках, поэтому я бы принял Linux-ориентированный ответ.

Каждый IP-адрес будет доступен только на одном (возможно, виртуальном) сетевом устройстве, и наоборот, поэтому достаточно было бы перечислить сетевые устройства и получить связанные IP-адреса, хотя я бы не очень обрадовался этому. (Дополнительные вопросы: возможно ли даже связать несколько IP-адресов с одним устройством? Как насчет одного и того же IP-адреса на нескольких устройствах? Не важно.)

Недостаточно решений:

  • gethostname() / gethostbyname() (как этот вопрос ). Используя этот метод, я всегда получаю 127.0.0.1 обратно (или .1.1 в Debian). Я подозреваю, что это потому, что имя хоста машины находится в файле hosts, и это проверяет gethostbyname(). (Я думаю, именно поэтому в Debian я всегда получаю 127.0.1.1: по умолчанию Debian добавляет localhost как 127.0.0.1 и имя хоста компьютера как 127.0.1.1 в файл hosts, верно?) Мне бы хотелось решение, которое игнорирует hosts и дает мне все на самом деле там.
  • Мне повезло больше getaddrinfo(), чем gethostname() / gethostbyname(). Кажется, связана та же проблема. Я проверил это, передав имя хоста машины и сервис (порт) NULL; В документах говорится, что передача NULL имени хоста И NULL службы недопустима, и это подтверждается тестированием. Не уверен, как еще спросить это обо всем на машине, но я открыт для предложений в этом ключе.
  • РЕДАКТИРОВАТЬ: этот ответ показывает, как получить IP-адрес из имени устройства, но не показывает, как перечислить имена устройств. Есть идеи?

ЗАКЛЮЧИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ: Я принял ответ Кески , чтобы дать ему кредит за указание на то, как это нужно сделать. Я опубликовал свой собственный ответ с указанием исходного кода того, как именно это сделать, если кому-то еще это понадобится.

Ответы [ 6 ]

7 голосов
/ 22 июля 2009

Вот мое доказательство концепции кода с использованием принятого ответа Каски , для потомков:

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>


static const char * flags(int sd, const char * name)
{
    static char buf[1024];

    static struct ifreq ifreq;
    strcpy(ifreq.ifr_name, name);

    int r = ioctl(sd, SIOCGIFFLAGS, (char *)&ifreq);
    assert(r == 0);

    int l = 0;
#define FLAG(b) if(ifreq.ifr_flags & b) l += snprintf(buf + l, sizeof(buf) - l, #b " ")
    FLAG(IFF_UP);
    FLAG(IFF_BROADCAST);
    FLAG(IFF_DEBUG);
    FLAG(IFF_LOOPBACK);
    FLAG(IFF_POINTOPOINT);
    FLAG(IFF_RUNNING);
    FLAG(IFF_NOARP);
    FLAG(IFF_PROMISC);
    FLAG(IFF_NOTRAILERS);
    FLAG(IFF_ALLMULTI);
    FLAG(IFF_MASTER);
    FLAG(IFF_SLAVE);
    FLAG(IFF_MULTICAST);
    FLAG(IFF_PORTSEL);
    FLAG(IFF_AUTOMEDIA);
    FLAG(IFF_DYNAMIC);
#undef FLAG

    return buf;
}

int main(void)
{
    static struct ifreq ifreqs[32];
    struct ifconf ifconf;
    memset(&ifconf, 0, sizeof(ifconf));
    ifconf.ifc_req = ifreqs;
    ifconf.ifc_len = sizeof(ifreqs);

    int sd = socket(PF_INET, SOCK_STREAM, 0);
    assert(sd >= 0);

    int r = ioctl(sd, SIOCGIFCONF, (char *)&ifconf);
    assert(r == 0);

    for(int i = 0; i < ifconf.ifc_len/sizeof(struct ifreq); ++i)
    {
        printf("%s: %s\n", ifreqs[i].ifr_name, inet_ntoa(((struct sockaddr_in *)&ifreqs[i].ifr_addr)->sin_addr));
        printf(" flags: %s\n", flags(sd, ifreqs[i].ifr_name));
    }

    close(sd);

    return 0;
}

Работает как шарм!

6 голосов
/ 21 июля 2009

Это можно сделать только в зависимости от операционной системы. Вы можете попытаться разобрать вывод 'iptables', но ответ right для linux - использовать ioctl.

SIOCGIFCONF  takes  a  struct  ifconf *.  The ifc_buf field points to a
      buffer of length ifc_len bytes, into which the kernel writes a list of
      type struct ifreq [].

Структура ifreq описана в linux / if.h:

struct ifreq 
{
#define IFHWADDRLEN     6
        union
        {
                char    ifrn_name[IFNAMSIZ];            /* if name, e.g. "en0" */
        } ifr_ifrn;

        union {
                struct  sockaddr ifru_addr;
                struct  sockaddr ifru_dstaddr;
                struct  sockaddr ifru_broadaddr;
                struct  sockaddr ifru_netmask;
                struct  sockaddr ifru_hwaddr;
                short   ifru_flags;
                int     ifru_ivalue;
                int     ifru_mtu;
                struct  ifmap ifru_map;
                char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
                char    ifru_newname[IFNAMSIZ];
                void *  ifru_data;
                struct  if_settings ifru_settings;
        } ifr_ifru;
};

Как видите, он содержит информацию об адресе, которую вы желаете.

1 голос
/ 21 июля 2009

Вы можете получить информацию об интерфейсе, требуемую несколькими способами, включая вызов ioctl () с опцией SIOCGIFCONF и цикл по возвращенным структурам для получения информации об адресе интерфейса.

Учитывая список всех IP-адресов, как бы я отфильтровал адреса обратной связи?

См. Структуру ifreq в ответе Кэски. Вы можете определить петлю (правильно) с помощью:

if (ifru_flags & IFF_LOOPBACK) 

Константы в if.h

1 голос
/ 21 июля 2009

Некоторые ответы на дополнительные вопросы:

  • Добавление нескольких IP-адресов на устройство можно выполнить с помощью псевдонимов. Linux создает устройства с именем eth0: 0, когда вы делаете это.

    ifconfig eth0:0 10.0.0.1

  • Наличие одного и того же IP-адреса под несколькими устройствами можно сделать с помощью объединения каналов / объединения каналов.

0 голосов
/ 21 июля 2009

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

Посмотрите на исходный код lsof и netstat . Вы увидите, что это включает обход структур памяти ядра, а не просто выполнение системных вызовов.

0 голосов
/ 21 июля 2009

Вы уверены, что правильно используете gethostname () / gethostbyname ()? проверьте здесь , единственная проблема, с которой я сталкиваюсь при этом, состоит в том, что возможно, что доменное имя имеет несколько сопоставленных IP-адресов. Если это так, то нет никакого способа узнать, какой IP-адрес принадлежит локальной машине:

...