Uzyskaj adres IP urządzenia

94

To pytanie jest prawie takie samo, jak wcześniej zadane Uzyskaj adres IP komputera lokalnego - Pytanie. Jednak muszę znaleźć adres (y) IP komputera z systemem Linux .

A więc: Jak - programowo w C ++ - wykryć adresy IP serwera linux, na którym działa moja aplikacja. Serwery będą miały co najmniej dwa adresy IP i potrzebuję konkretnego (tego w danej sieci (publicznej)).

Jestem pewien, że jest do tego prosta funkcja - ale gdzie?


Aby było trochę jaśniej:

  • Serwer będzie miał oczywiście adres „localhost”: 127.0.0.1
  • Serwer będzie miał wewnętrzny (zarządzający) adres IP: 172.16.xx
  • Serwer będzie miał zewnętrzny (publiczny) adres IP: 80.190.xx

Muszę znaleźć zewnętrzny adres IP, aby powiązać z nim moją aplikację. Oczywiście mogę też powiązać się z INADDR_ANY (a właściwie to właśnie robię w tej chwili). Wolałbym jednak wykryć adres publiczny.

BlaM
źródło
3
Po co oznaczać popen z ifconfig? To naprawdę słuszna decyzja. Biblioteki się zmieniają, ale ifconfig i popen zawsze tam będą.
Erik Aronesty
3
Nie ifconfig, routeetc są przestarzałe dla ipkomendy. Teraz powinieneś tego użyć.
Anders
ifconfig nadal działa na większej liczbie architektur niż jakiekolwiek inne podejście. Więc zmień to na polecenie ip. Kiedy / jeśli to konieczne. popen jest nadal lepszym rozwiązaniem.
Erik Aronesty,

Odpowiedzi:

104

Znalazłem problematyczne rozwiązanie ioctl na OS X (które jest zgodne z POSIX, więc powinno być podobne do Linuksa). Jednak getifaddress () pozwoli ci zrobić to samo z łatwością, działa dobrze dla mnie na os x 10.5 i powinno być takie samo poniżej.

Zrobiłem poniżej szybki przykład, który wyświetli cały adres IPv4 maszyny (powinieneś również sprawdzić, czy getifaddrs się powiodło, tj. Zwraca 0).

Zaktualizowałem, że pokazuje również adresy 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;
}
Dwanaście47
źródło
7
+1 i dzięki za wprowadzenie tego miłego podejścia zamiast tych nudnych ioctl! Jednak twoja obsługa IP6 jest nadal niepoprawna - powinieneś rzucić na sockaddr_in6, czyli coś w stylutmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Andrey
2
@Andrey, dzięki. Zaktualizowałem moją odpowiedź (chociaż nie miałem okazji jej przetestować)
Dwanaście47
2
daje loi wlan0interfejsy w moim przypadku. To, czego potrzebuję, to wlan0lub to, eth0co ma połączenie z Internetem, więc strcmpczy powinienem użyć, czy jest lepszy sposób?
Sudip Bhattarai
28
  1. Utwórz gniazdo.
  2. Wykonać ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Przeczytaj /usr/include/linux/if.hinformacje o strukturach ifconfi ifreq. Powinno to dać adres IP każdego interfejsu w systemie. Przeczytaj także /usr/include/linux/sockios.ho dodatkowych ioctls.

Steve Baker
źródło
Wydawało mi się, że poda tylko adres interfejsu, z którym się łączy.
Matt Joiner,
@MattJoiner Nie, spójrz na stronę man, SIOCGIFCONF zwraca informacje o wszystkich interfejsach. Cytowana tutaj część strony podręcznika: "Zwróć listę adresów interfejsów (warstwy transportowej). Obecnie oznacza to tylko adresy z rodziny AF_INET (IPv4) w celu zapewnienia zgodności. Użytkownik przekazuje strukturę ifconf jako argument do ioctl. Zawiera wskaźnik do tablicy struktur ifreq w ifc_req i jej długości w bajtach w ifc_len. Jądro wypełnia ifreqs wszystkimi aktualnymi adresami interfejsów L3, które są uruchomione "
Stéphane
25

Podoba mi się odpowiedź jjvainio . Jak mówi Zan Lnyx, używa lokalnej tablicy routingu do znalezienia adresu IP interfejsu Ethernet, który byłby używany do połączenia z określonym hostem zewnętrznym. Korzystając z podłączonego gniazda UDP, można uzyskać informacje bez wysyłania żadnych pakietów. Takie podejście wymaga wybrania określonego hosta zewnętrznego. W większości przypadków każdy dobrze znany publiczny adres IP powinien załatwić sprawę. W tym celu podoba mi się adres publicznego serwera DNS Google 8.8.8.8, ale może się zdarzyć, że będziesz chciał wybrać inny zewnętrzny adres IP hosta. Oto kod, który ilustruje pełne podejście.

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);
}
4dan
źródło
2
to daje mi prywatny adres IP 127. *. *. *. może dlatego, że jestem za NATem?
Eugene
14

Jak już wiesz, nie ma czegoś takiego jak pojedynczy „lokalny adres IP”. Oto jak znaleźć lokalny adres, który można wysłać do określonego hosta.

  1. Utwórz gniazdo UDP
  2. Podłącz gniazdo do adresu zewnętrznego (hosta, który ostatecznie otrzyma adres lokalny)
  3. Użyj getsockname, aby uzyskać lokalny adres
jjvainio
źródło
Nigdy nie robiłem tego w ten sposób, ale lubię to. Używa automatycznie tablicy routingu systemu operacyjnego i jest znacznie łatwiejsza niż skanowanie listy interfejsów.
Zan Lynx
5
Zakłada się, że wiesz, jaki będzie adres zewnętrzny. Często tak nie jest w centrach danych.
Christopher,
Zawsze możesz wypróbować trzy, które są przypisane do różnych kontynentów (IANA to ułatwia) i zaakceptować dwa najlepsze z trzech.
Joshua
9

Ma to tę zaletę, że działa na wielu odmianach unixa ... i można go zmodyfikować w trywialny sposób, aby działał na dowolnym systemie operacyjnym. Wszystkie powyższe rozwiązania dają mi błędy kompilatora w zależności od fazy księżyca. W chwili, gdy istnieje dobry sposób POSIX, aby to zrobić ... nie używaj tego (w czasie, gdy to było pisane, tak nie było).

// 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;
}
Erik Aronesty
źródło
dziękuję za to rozwiązanie, działa dla mnie świetnie i jest bardzo przydatne w wielu sytuacjach, gdy chcesz tylko pobrać dane z programów cl
zitroneneis
Chociaż ifconfig wyświetla wymagane dane, zdecydowanie odradzam używanie jakiegokolwiek programu w ten sposób. O wiele lepiej jest rzeczywiście dostać się do źródła ifconfig i uzyskać odpowiednią funkcjonalność, niż uruchomić je i przeanalizować jego dane wyjściowe.
Zaur Nasibov
1
@MartinMeeser: uczynił go neutralnym językowo, wyszukując oddzielnie „inet”, a następnie „:”.
Erik Aronesty
1
Następnie masz inne języki, które nie są nawet oparte na zestawach znaków łacińskich. Rozwiązaniem jest nie używanie narzędzi poleceń, ponieważ stają się one przestarzałe, podobnie jak ifconfig. Powinieneś użyć ipteraz. Właściwy sposób generować moc języka neutralnego jest ustawienie LANGzmiennej środowiskowej do C, jak:LANG=C ls -l
Anders
1
Dzięki. To proste i niesamowite rozwiązanie!
Ahmad Afkandeh
5

Nie sądzę, aby istniała ostateczna prawidłowa odpowiedź na twoje pytanie. Zamiast tego istnieje wiele sposobów, jak zbliżyć się do tego, co chcesz. Dlatego podam kilka wskazówek, jak to zrobić.

Jeśli maszyna ma więcej niż 2 interfejsy ( loliczy się jako jeden), będziesz mieć problemy z łatwym automatycznym wykrywaniem właściwego interfejsu. Oto kilka przepisów, jak to zrobić.

Na przykład problem polega na tym, że hosty znajdują się w strefie DMZ za firewallem NAT, który zmienia publiczny adres IP na prywatny i przekazuje żądania. Twój komputer może mieć 10 interfejsów, ale tylko jeden odpowiada publicznemu.

Nawet autodetekcja nie działa w przypadku podwójnego NAT, w którym zapora sieciowa tłumaczy nawet źródłowy adres IP na coś zupełnie innego. Nie możesz więc być nawet pewien, że trasa domyślna prowadzi do twojego interfejsu z interfejsem publicznym.

Wykryj go domyślną trasą

To jest mój zalecany sposób automatycznego wykrywania rzeczy

Coś jak ip r get 1.1.1.1zwykle mówi ci interfejs, który ma domyślną trasę.

Jeśli chcesz odtworzyć to w swoim ulubionym języku skryptów / programowania, użyj strace ip r get 1.1.1.1żółtej cegły i postępuj zgodnie z nią.

Ustaw to za pomocą /etc/hosts

To jest moja rada, jeśli chcesz zachować kontrolę

Możesz utworzyć wpis w /etc/hostspolubieniu

80.190.1.3 publicinterfaceip

Następnie możesz użyć tego aliasu, publicinterfaceipaby odnieść się do swojego interfejsu publicznego.

Niestety haproxynie rozwiązuje tej sztuczki w przypadku IPv6

Korzystaj ze środowiska

To jest dobre obejście, /etc/hostsjeśli tak nie jestroot

Tak samo jak /etc/hosts. ale użyj do tego środowiska. Możesz spróbować /etc/profilelub ~/.profiledo tego.

Dlatego jeśli twój program potrzebuje zmiennej MYPUBLICIP, możesz dołączyć kod taki jak (to jest C, możesz stworzyć z niego C ++):

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

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

Możesz więc wywołać swój skrypt / program w /path/to/your/scriptten sposób

MYPUBLICIP=80.190.1.3 /path/to/your/script

to działa nawet w crontab.

Wylicz wszystkie interfejsy i wyeliminuj te, których nie chcesz

Desperacki sposób, jeśli nie możesz użyć ip

Jeśli wiesz, czego nie chcesz, możesz wyliczyć wszystkie interfejsy i zignorować wszystkie fałszywe.

Tutaj wydaje się już odpowiedź https://stackoverflow.com/a/265978/490291 dla tego podejścia.

Zrób to jak DLNA

Droga pijaka, który próbuje utopić się w alkoholu

Możesz spróbować wyliczyć wszystkie bramy UPnP w Twojej sieci iw ten sposób znaleźć właściwą trasę dla jakiejś „zewnętrznej” rzeczy. Może to być nawet na trasie, na którą nie wskazuje Twoja domyślna trasa.

Więcej na ten temat można znaleźć na stronie https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

Daje to dobre wrażenie, który z nich jest Twoim prawdziwym publicznym interfejsem, nawet jeśli domyślna trasa prowadzi gdzie indziej.

Jest ich jeszcze więcej

Gdzie góra spotyka proroka

Routery IPv6 ogłaszają się, podając właściwy prefiks IPv6. Spojrzenie na prefiks daje wskazówkę, czy ma jakiś wewnętrzny adres IP, czy globalny.

Możesz nasłuchiwać ramek IGMP lub IBGP, aby znaleźć odpowiednią bramę.

Istnieje mniej niż 2 ^ 32 adresów IP. Dlatego w sieci LAN po prostu pingowanie ich wszystkich nie zajmuje dużo czasu. To daje Ci statystyczną wskazówkę, gdzie z Twojego punktu widzenia znajduje się większość Internetu. Jednak powinieneś być nieco bardziej rozsądny niż słynny https://de.wikipedia.org/wiki/SQL_Slammer

ICMP, a nawet ARP są dobrymi źródłami informacji o paśmie bocznym sieci. To też może ci pomóc.

Możesz użyć adresu rozgłoszeniowego Ethernet, aby skontaktować się ze wszystkimi urządzeniami infrastruktury sieciowej, które często pomagają, jak DHCP (nawet DHCPv6) i tak dalej.

Ta dodatkowa lista jest prawdopodobnie nieskończona i zawsze niekompletna, ponieważ każdy producent urządzeń sieciowych zajmuje się wymyślaniem nowych luk w zabezpieczeniach dotyczących automatycznego wykrywania własnych urządzeń. Co często bardzo pomaga w wykrywaniu publicznego interfejsu, którego nie powinno być.

- powiedział Nuff. Na zewnątrz.

Tino
źródło
4

W nawiązaniu do tego, co powiedział Steve Baker, opis SIOCGIFCONF ioctl można znaleźć na stronie podręcznika netdevice (7) .

Gdy masz już listę wszystkich adresów IP na hoście, będziesz musiał użyć logiki specyficznej dla aplikacji, aby odfiltrować adresy, których nie chcesz i mieć nadzieję, że został Ci jeden adres IP.

camh
źródło
4

Nie koduj tego na sztywno: to może się zmienić. Wiele programów wymyśla, z czym się wiązać, czytając plik konfiguracyjny i robiąc wszystko, co mówi. W ten sposób, jeśli Twój program w przyszłości będzie musiał powiązać się z czymś, co nie jest publicznym adresem IP, może to zrobić.

Thanatos
źródło
2

Ponieważ pytanie określa Linuksa, moją ulubioną techniką odkrywania adresów IP maszyny jest użycie netlink. Tworząc gniazdo netlink protokołu NETLINK_ROUTE i wysyłając RTM_GETADDR, Twoja aplikacja otrzyma wiadomość (y) zawierające wszystkie dostępne adresy IP. Przykładem warunkiem jest tutaj .

Aby uprościć część obsługi wiadomości, wygodna jest biblioteka libmnl. Jeśli chcesz dowiedzieć się więcej o różnych opcjach NETLINK_ROUTE (i o tym, jak są one analizowane), najlepszym źródłem jest kod źródłowy iproute2 (zwłaszcza aplikacja monitorująca), a także funkcje odbierające w jądrze. Strona podręcznika rtnetlink również zawiera przydatne informacje.

Kristian Evensen
źródło
1

Integrację z curl można przeprowadzić w tak prosty sposób, jak: curl www.whatismyip.orgz powłoki dostaniesz globalne IP. Jesteś w pewnym sensie zależny od zewnętrznego serwera, ale zawsze będziesz za NATem.


źródło
Nigdy nie rozumiem, dlaczego więcej osób nie korzysta z tej witryny. Mają nawet stronę do odczytu maszynowego, więc nie musisz przeglądać informacji.
deft_code
1
Możesz nie uzyskać prawidłowego adresu IP, jeśli pracujesz w proxy.
Jack,
1
icanhazip.com zwraca adres IP i nic więcej. Idealny do osadzania w skrypcie bash!
lukecyca
ipinfo.io/ip to kolejna alternatywa. Jeśli zwijasz ipinfo.io , zwraca on również nazwę hosta, informacje o geolokalizacji i inne dane w formacie 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();

}

Chaza
źródło
10
Jest to ciężkie, przeznaczone tylko dla systemu Windows (pytanie o Linuksa), źle sformatowane rozwiązanie polegające na usługach pozostających poza lokalną kontrolą poprzez delikatną analizę zawartości witryny. Nie mogę znaleźć żadnej pozytywnej strony tego „rozwiązania”.
viraptor
1
Ha! Analiza HTML szuka nawet komentarza, który jest ostrzeżeniem o tym, co jest robione.
notlesh