Получить IP-адрес машины - PullRequest
       37

Получить IP-адрес машины

87 голосов
/ 17 октября 2008

Этот вопрос почти такой же, как ранее задаваемый Получить IP-адрес локального компьютера -Вопрос. Однако мне нужно найти IP-адрес (а) Linux Machine .

Итак: Как мне - программным способом на C ++ - определить IP-адреса сервера linux, на котором запущено мое приложение. Серверы будут иметь как минимум два IP-адреса, и мне нужен конкретный (тот, что в данной сети (общедоступный)).

Я уверен, что для этого есть простая функция, но где?


Чтобы прояснить ситуацию:

  • Сервер, очевидно, будет иметь "localhost": 127.0.0.1
  • Сервер будет иметь внутренний (управляющий) IP-адрес: 172.16.x.x
  • Сервер будет иметь внешний (общедоступный) IP-адрес: 80.190.x.x

Мне нужно найти внешний IP-адрес, чтобы привязать к нему мое приложение. Очевидно, что я также могу связываться с INADDR_ANY (и на самом деле это то, что я делаю в данный момент). Я бы предпочел определить публичный адрес.

Ответы [ 12 ]

98 голосов
/ 05 ноября 2008

Я обнаружил, что решение ioctl проблематично для os x (которое совместимо с POSIX, поэтому должно быть похоже на linux). Однако getifaddress () позволит вам легко сделать то же самое, он отлично работает для меня на OS X 10,5 и должен быть таким же ниже.

Я сделал быстрый пример ниже, который напечатает весь IPv4-адрес машины (вы также должны проверить, что getifaddrs был успешным, т.е. возвращает 0).

Я обновил это, показать адреса IPv6 тоже.

#include <stdio.h>      
#include <sys/types.h>
#include <ifaddrs.h>
#include <netinet/in.h> 
#include <string.h> 
#include <arpa/inet.h>

int main (int argc, const char * argv[]) {
    struct ifaddrs * ifAddrStruct=NULL;
    struct ifaddrs * ifa=NULL;
    void * tmpAddrPtr=NULL;

    getifaddrs(&ifAddrStruct);

    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr) {
            continue;
        }
        if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
            // is a valid IP4 Address
            tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addressBuffer[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
            // is a valid IP6 Address
            tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addressBuffer[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
            printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); 
        } 
    }
    if (ifAddrStruct!=NULL) freeifaddrs(ifAddrStruct);
    return 0;
}
28 голосов
/ 17 октября 2008
  1. Создать сокет.
  2. Выполнить ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Прочтите /usr/include/linux/if.h для получения информации о структурах ifconf и ifreq. Это должно дать вам IP-адрес каждого интерфейса в системе. Также прочитайте /usr/include/linux/sockios.h для дополнительных ioctls.

24 голосов
/ 25 июня 2010

Мне нравится ответ jjvainio . Как говорит Zan Lnyx, он использует локальную таблицу маршрутизации, чтобы найти IP-адрес интерфейса Ethernet, который будет использоваться для соединения с конкретным внешним хостом. Используя подключенный сокет UDP, вы можете получить информацию, не отправляя пакеты. Подход требует, чтобы вы выбрали конкретный внешний хост. Большую часть времени любой известный публичный IP должен делать свое дело. Для этой цели мне нравится общедоступный адрес DNS-сервера Google 8.8.8.8, но иногда может потребоваться выбрать другой IP-адрес внешнего хоста. Вот некоторый код, который иллюстрирует полный подход.

void GetPrimaryIp(char* buffer, size_t buflen) 
{
    assert(buflen >= 16);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sock != -1);

    const char* kGoogleDnsIp = "8.8.8.8";
    uint16_t kDnsPort = 53;
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr(kGoogleDnsIp);
    serv.sin_port = htons(kDnsPort);

    int err = connect(sock, (const sockaddr*) &serv, sizeof(serv));
    assert(err != -1);

    sockaddr_in name;
    socklen_t namelen = sizeof(name);
    err = getsockname(sock, (sockaddr*) &name, &namelen);
    assert(err != -1);

    const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, buflen);
    assert(p);

    close(sock);
}
14 голосов
/ 18 октября 2008

Как вы выяснили, такого понятия, как "локальный IP-адрес", не существует. Вот как узнать локальный адрес, который можно отправить конкретному хосту.

  1. Создание сокета UDP
  2. Подключите сокет к внешнему адресу (хост, который в конечном итоге получит локальный адрес)
  3. Используйте getsockname для получения локального адреса
8 голосов
/ 18 мая 2011

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

// ifconfig | perl -ne 'print "$1\n" if /inet addr:([\d.]+)/'

#include <stdlib.h>

int main() {
        setenv("LANG","C",1);
        FILE * fp = popen("ifconfig", "r");
        if (fp) {
                char *p=NULL, *e; size_t n;
                while ((getline(&p, &n, fp) > 0) && p) {
                   if (p = strstr(p, "inet ")) {
                        p+=5;
                        if (p = strchr(p, ':')) {
                            ++p;
                            if (e = strchr(p, ' ')) {
                                 *e='\0';
                                 printf("%s\n", p);
                            }
                        }
                   }
              }
        }
        pclose(fp);
        return 0;
}
4 голосов
/ 23 ноября 2015

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

Если машина имеет более 2 интерфейсов (lo считается одним), у вас будут проблемы с автоматическим определением правильного интерфейса. Вот несколько рецептов, как это сделать.

Проблема, например, заключается в том, что хосты находятся в DMZ за брандмауэром NAT, который изменяет публичный IP на какой-то частный IP и пересылает запросы. Ваша машина может иметь 10 интерфейсов, но только один соответствует общедоступному.

Даже автоопределение не работает, если вы используете двойной NAT, где ваш брандмауэр даже переводит исходный IP в нечто совершенно иное. Так что вы даже не можете быть уверены, что маршрут по умолчанию ведет к вашему интерфейсу с открытым интерфейсом.

Обнаружение по маршруту по умолчанию

Это мой рекомендуемый способ автоматического определения вещей

Что-то вроде ip r get 1.1.1.1 обычно говорит вам интерфейс, который имеет маршрут по умолчанию.

Если вы хотите воссоздать это на своем любимом языке сценариев / программирования, используйте strace ip r get 1.1.1.1 и следуйте по дороге из желтого кирпича.

Установите его с помощью /etc/hosts

Это моя рекомендация, если вы хотите сохранить контроль

Вы можете создать запись в /etc/hosts как

80.190.1.3 publicinterfaceip

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

К сожалению haproxy не использует этот трюк с IPv6

Использование окружающей среды

Это хороший обходной путь для /etc/hosts, если вы не root

То же, что и /etc/hosts. но используйте для этого окружающую среду. Вы можете попробовать /etc/profile или ~/.profile для этого.

Следовательно, если вашей программе нужна переменная MYPUBLICIP, вы можете включить в нее такой код (это C, не стесняйтесь создавать из него C ++):

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

if (!mypublicip) { fprintf(stderr, "please set environment variable %s\n", MYPUBLICIPENVVAR); exit(3); }

Итак, вы можете назвать свой скрипт / программу /path/to/your/script вот так

MYPUBLICIP=80.190.1.3 /path/to/your/script

это даже работает в crontab.

Перечислите все интерфейсы и исключите те, которые вам не нужны

Отчаянный путь, если вы не можете использовать ip

Если вы знаете, чего не хотите, вы можете перечислить все интерфейсы и игнорировать все ложные.

Здесь, кажется, уже есть ответ https://stackoverflow.com/a/265978/490291 для этого подхода.

Делай как DLNA

Путь пьяного человека, который пытается утопиться в алкоголе

Вы можете попытаться перечислить все шлюзы UPnP в вашей сети и таким образом найти правильный маршрут для некоторой «внешней» вещи. Это может быть даже маршрут, на который маршрут по умолчанию не указывает.

Подробнее об этом, возможно, см. https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

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

Есть еще больше

Где гора встречает пророка

Маршрутизаторы IPv6 объявляют себя, чтобы дать вам правильный префикс IPv6. Просмотр префикса дает подсказку о том, имеет ли он внутренний или глобальный IP-адрес.

Вы можете прослушивать кадры IGMP или IBGP, чтобы найти подходящий шлюз.

Существует менее 2 ^ 32 IP-адресов. Следовательно, в локальной сети не требуется много времени, чтобы просто пропинговать их всех. Это дает вам статистическую подсказку о том, где находится большая часть Интернета с вашей точки зрения. Однако вы должны быть немного более разумным, чем знаменитый https://de.wikipedia.org/wiki/SQL_Slammer

ICMP и даже ARP являются хорошими источниками информации о боковой полосе сети. Это может помочь вам.

Вы можете использовать широковещательный адрес Ethernet для связи со всеми устройствами сетевой инфраструктуры, которые часто выручают, такими как DHCP (даже DHCPv6) и т. Д.

Этот дополнительный список, вероятно, бесконечен и всегда неполон, потому что каждый производитель сетевых устройств активно изобретает новые дыры в безопасности, связанные с автоматическим обнаружением их собственных устройств. Что часто очень помогает в обнаружении публичного интерфейса там, где его нет.

'Нуфф сказал. Out.

4 голосов
/ 12 июля 2010

Не задавайте это жестко: это то, что может измениться. Многие программы выясняют, с чем связываться, читая файл конфигурации и делая то, что говорит. Таким образом, если ваша программа когда-нибудь в будущем будет привязана к чему-то, что не является публичным IP, она может сделать это.

4 голосов
/ 18 октября 2008

В дополнение к тому, что сказал Стив Бейкер, вы можете найти описание SIOCGIFCONF ioctl на справочной странице netdevice (7) .

После того, как у вас есть список всех IP-адресов на хосте, вам придется использовать специфическую логику приложения для фильтрации нежелательных адресов и надеяться, что у вас остался один IP-адрес.

1 голос
/ 28 июня 2013

Поскольку вопрос касается Linux, моя любимая техника для определения IP-адресов машины - использовать netlink. Создав сокет netlink протокола NETLINK_ROUTE и отправив RTM_GETADDR, ваше приложение получит сообщение (я), содержащее все доступные IP-адреса. Пример приводится здесь .

Для упрощения обработки сообщений удобно использовать libmnl. Если вам интересно узнать больше о различных параметрах NETLINK_ROUTE (и как они анализируются), лучшим источником будет исходный код iproute2 (особенно приложения монитора), а также функции приема ядро. Страница man rtnetlink также содержит полезную информацию.

1 голос
/ 17 октября 2008

Вы можете сделать некоторую интеграцию с curl так же просто, как: curl www.whatismyip.org из оболочки даст вам ваш глобальный IP Вы как бы зависите от какого-то внешнего сервера, но всегда будете, если будете за NAT.

...