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

94

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

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

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


Чтобы было немного понятнее:

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

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

BlaM
источник
3
Зачем уменьшать размер окна ifconfig? Это действительно правильный поступок. Библиотеки меняются, но ifconfig и popen будут всегда.
Эрик Аронести
3
Нет, ifconfigи routeт. Д. Для этой ipкоманды устарели . Теперь вы должны использовать это вместо этого.
Андерс
ifconfig по-прежнему работает на большем количестве архитектур, чем любой другой подход. Так что измените его на ip command.when / если необходимо. popen по-прежнему является лучшим решением.
Эрик Аронести

Ответы:

104

Я обнаружил, что решение 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;
}
Двенадцать47
источник
7
+1 и спасибо за этот хороший подход вместо этих скучных ioctl! Однако ваша обработка IP6 по-прежнему некорректна - вы должны использовать sockaddr_in6, т.е. что-то вродеtmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Андрей
2
@ Андрей, спасибо. Я обновил свой ответ (хотя у меня не было возможности проверить его)
Twelve47
2
дает loи wlan0интерфейсы в моем случае. Мне нужно wlan0или eth0есть подключение к Интернету. Так что, что мне strcmpлучше использовать?
Судип Бхаттарай,
28
  1. Создайте сокет.
  2. Выполнить ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

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

Стив Бейкер
источник
У меня создалось впечатление, что он даст вам только адрес интерфейса, к которому он привязан.
Matt Joiner
@MattJoiner Нет, взгляните на страницу руководства, SIOCGIFCONF возвращает информацию по всем интерфейсам. Часть процитированной здесь страницы руководства: «Возвращает список адресов интерфейса (транспортного уровня). В настоящее время для совместимости это означает только адреса семейства AF_INET (IPv4). Пользователь передает структуру ifconf в качестве аргумента ioctl. Она содержит указатель на массив структур ifreq в ifc_req и его длина в байтах в ifc_len. Ядро заполняет ifreq всеми текущими адресами интерфейса L3, которые работают "
Стефан
25

Мне нравится ответ jjvainio . Как говорит Зан Линкс, он использует локальную таблицу маршрутизации, чтобы найти 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);
}
4дан
источник
2
это дает мне приватный ip 127. *. *. *. может потому что я за NAT?
Евгений
14

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

  1. Создать сокет UDP
  2. Подключите сокет к внешнему адресу (хост, который в конечном итоге получит локальный адрес)
  3. Используйте getsockname, чтобы получить локальный адрес
Jjvainio
источник
Я никогда так не делал, но мне это нравится. Он автоматически использует таблицу маршрутизации ОС и намного проще, чем сканирование списка интерфейсов.
Zan Lynx
5
Это предполагает, что вы знаете, каким будет внешний адрес. В центрах обработки данных часто бывает не так.
Кристофер
Вы всегда можете попробовать три, которые назначены разным континентам (IANA упрощает это), и выбрать лучшие два из трех.
Джошуа
9

Это имеет то преимущество, что работает со многими разновидностями unix ... и вы можете тривиально изменить его для работы с любыми o / s. Все приведенные выше решения дают мне ошибки компилятора в зависимости от фазы луны. В тот момент, когда есть хороший способ 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;
}
Эрик Аронести
источник
спасибо за это решение, отлично работает для меня и очень удобно во многих ситуациях, когда вы просто хотите получить данные из cl-программ
zitroneneis 09
Хотя ifconfig выводит требуемые данные, я настоятельно не рекомендую использовать любую программу таким образом. Намного лучше залезть в исходный код ifconfig и получить функциональность, чем запустить его и проанализировать его вывод.
Заур Насибов
1
@MartinMeeser: сделал его нейтральным по отношению к языку, ища "inet", затем ":" отдельно.
Эрик Аронести
1
Тогда у вас есть другие языки, которые даже не являются набором символов на основе латиницы. Решение состоит в том, чтобы не использовать командные инструменты, поскольку они устарели, как ifconfig. Вы должны использовать ipсейчас. Правильный способ генерации языка нейтральный вывод является заданной LANGпеременной окружения C, как:LANG=C ls -l
Anders
1
Спасибо. Это простое и отличное решение!
Ахмад Афканде,
5

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

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

Проблема, например, в том, что хосты находятся в DMZ за брандмауэром NAT, который меняет общедоступный 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) и так далее.

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

'Достаточно. Вне.

Тино
источник
4

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

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

camh
источник
4

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

Танатос
источник
2

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

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

Кристиан Эвенсен
источник
1

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


источник
Я никогда не понимаю, почему все больше людей не используют этот сайт. У них даже есть машинно-читаемая страница, поэтому вам не нужно выводить информацию на экран.
deft_code
1
Вы можете не получить правильный IP-адрес, если работаете внутри прокси.
Джек
1
icanhazip.com возвращает IP и ничего более. Идеально подходит для встраивания в сценарий bash!
lukecyca
ipinfo.io/ip - еще одна альтернатива. Если вы скручиваете ipinfo.io, он также возвращает имя хоста, информацию о геолокации и многое другое в формате JSON.
Ben Dowling
-10

// Use a HTTP request to a well known server that echo's back the public IP address void GetPublicIP(CString & csIP) { // Initialize COM bool bInit = false; if (SUCCEEDED(CoInitialize(NULL))) { // COM was initialized bInit = true;

    // Create a HTTP request object
    MSXML2::IXMLHTTPRequestPtr HTTPRequest;
    HRESULT hr = HTTPRequest.CreateInstance("MSXML2.XMLHTTP");
    if (SUCCEEDED(hr))
    {
        // Build a request to a web site that returns the public IP address
        VARIANT Async;
        Async.vt = VT_BOOL;
        Async.boolVal = VARIANT_FALSE;
        CComBSTR ccbRequest = L"http://whatismyipaddress.com/";

        // Open the request
        if (SUCCEEDED(HTTPRequest->raw_open(L"GET",ccbRequest,Async)))
        {
            // Send the request
            if (SUCCEEDED(HTTPRequest->raw_send()))
            {
                // Get the response
                CString csRequest = HTTPRequest->GetresponseText();

                // Parse the IP address
                CString csMarker = "<!-- contact us before using a script to get your IP address -->";
                int iPos = csRequest.Find(csMarker);
                if (iPos == -1)
                    return;
                iPos += csMarker.GetLength();
                int iPos2 = csRequest.Find(csMarker,iPos);
                if (iPos2 == -1)
                    return;

                // Build the IP address
                int nCount = iPos2 - iPos;
                csIP = csRequest.Mid(iPos,nCount);
            }
        }
    }
}

// Unitialize COM
if (bInit)
    CoUninitialize();

}

Чаза
источник
10
Это тяжелое, предназначенное только для Windows (вопрос о Linux), плохо отформатированное решение, полагающееся на службы, находящиеся вне локального контроля, через хрупкий парсинг содержимого веб-сайта. Не могу найти ни одной положительной стороны этого «решения».
viraptor
1
Ха! Анализ HTML даже ищет комментарий, который является предупреждением о том, что именно происходит.
notlesh 01