Obtenha o endereço IP da máquina

94

Esta pergunta é quase igual à anterior Obtenha o endereço IP do computador local - pergunta. No entanto, preciso encontrar o (s) endereço (s) IP de uma máquina Linux .

Então: Como faço para - programaticamente em C ++ - detectar os endereços IP do servidor Linux em que meu aplicativo está sendo executado. Os servidores terão pelo menos dois endereços IP e eu preciso de um específico (o de uma determinada rede (a pública)).

Tenho certeza de que existe uma função simples para fazer isso - mas onde?


Para tornar as coisas um pouco mais claras:

  • O servidor obviamente terá o "localhost": 127.0.0.1
  • O servidor terá um endereço IP interno (gerenciamento): 172.16.xx
  • O servidor terá um endereço IP externo (público): 80.190.xx

Preciso encontrar o endereço IP externo para vincular meu aplicativo a ele. Obviamente, também posso vincular a INADDR_ANY (e na verdade é o que faço no momento). Eu preferiria detectar o endereço público, no entanto.

BlaM
fonte
3
Por que marcar o popen do ifconfig? É realmente a coisa certa a fazer. As bibliotecas mudam, mas ifconfig e popen sempre estarão lá.
Erik Aronesty
3
Não, ifconfig, routeetc são preteridos para o ipcomando. Agora você deve usar isso.
Anders
ifconfig ainda, hoje, funciona em mais arquiteturas do que qualquer outra abordagem. Portanto, mude para o comando ip.quando / se apropriado. popen ainda é a melhor solução.
Erik Aronesty

Respostas:

104

Achei a solução ioctl problemática no os x (que é compatível com POSIX, então deve ser semelhante ao Linux). No entanto getifaddress () permitirá que você faça a mesma coisa facilmente, ele funciona bem para mim no OS x 10.5 e deve ser o mesmo abaixo.

Fiz um exemplo rápido abaixo que imprimirá todos os endereços IPv4 da máquina (você também deve verificar se getifaddrs foi bem-sucedido, ou seja, retorna 0).

Eu o atualizei para mostrar os endereços IPv6 também.

#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;
}
Doze47
fonte
7
+1 e obrigado por trazer esta abordagem agradável em vez daqueles ioctls chatos! No entanto, sua manipulação de IP6 ainda está incorreta - você deve lançar para sockaddr_in6, ou seja, algo comotmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Andrey
2
@Andrey, obrigado. Atualizei minha resposta (não tive a chance de testá-la)
Twelve47,
2
loe wlan0interfaces no meu caso. O que eu exijo wlan0ou eth0tem conexão com a internet. Então, devo usar strcmpou há uma maneira melhor?
Sudip Bhattarai
28
  1. Crie um soquete.
  2. Executar ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Leia /usr/include/linux/if.hpara obter informações sobre as estruturas ifconfe ifreq. Isso deve fornecer o endereço IP de cada interface no sistema. Leia também /usr/include/linux/sockios.hpara ioctls adicionais.

Steve Baker
fonte
Tive a impressão de que só lhe daria o endereço da interface a que se liga.
Matt Joiner
@MattJoiner Não, dê uma olhada na página do manual, SIOCGIFCONF retorna informações sobre todas as interfaces. Parte da página do manual citada aqui: "Retorne uma lista de endereços de interface (camada de transporte). Isso atualmente significa apenas endereços da família AF_INET (IPv4) para compatibilidade. O usuário passa uma estrutura ifconf como argumento para o ioctl. Ela contém um ponteiro para uma matriz de estruturas ifreq em ifc_req e seu comprimento em bytes em ifc_len. O kernel preenche os ifreqs com todos os endereços de interface L3 atuais que estão sendo executados "
Stéphane
25

Gosto da resposta de jjvainio . Como Zan Lnyx diz, ele usa a tabela de roteamento local para encontrar o endereço IP da interface ethernet que seria usada para uma conexão a um host externo específico. Usando um soquete UDP conectado, você pode obter as informações sem realmente enviar nenhum pacote. A abordagem requer que você escolha um host externo específico. Na maioria das vezes, qualquer IP público conhecido deve resolver o problema. Eu gosto do endereço de servidor DNS público 8.8.8.8 do Google para essa finalidade, mas pode haver momentos em que você queira escolher um IP de host externo diferente. Aqui está um código que ilustra a abordagem completa.

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
fonte
2
isso me dá priviate ip 127. *. *. *. talvez porque eu esteja atrás de NAT?
Eugene,
14

Como você descobriu, não existe um único "endereço IP local". Veja como descobrir o endereço local que pode ser enviado a um host específico.

  1. Crie um soquete UDP
  2. Conecte o soquete a um endereço externo (o host que eventualmente receberá o endereço local)
  3. Use getsockname para obter o endereço local
Jjvainio
fonte
Nunca fiz assim, mas gosto. Ele usa a tabela de roteamento do sistema operacional automaticamente e é muito mais fácil do que examinar a lista de interfaces.
Zan Lynx
5
Isso pressupõe que você sabe o que será um endereço externo. Freqüentemente, esse não é o caso em data centers.
Christopher
Você sempre pode tentar três que são atribuídos a continentes diferentes (IANA torna isso mais fácil) e aceitar o melhor dois de três.
Joshua
9

Isso tem a vantagem de funcionar em muitos sabores de unix ... e você pode modificá-lo trivialmente para funcionar em qualquer o / s. Todas as soluções acima geram erros de compilador dependendo da fase da lua. No momento em que houver uma boa maneira POSIX de fazer isso ... não use isso (na época em que isso foi escrito, não era o caso).

// 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
fonte
obrigado por esta solução, funciona muito bem para mim e é muito útil em muitas situações quando você deseja apenas obter alguns dados de programas cl
zitroneneis
Embora ifconfig produza os dados necessários, eu desencorajo fortemente o uso de qualquer programa dessa forma. É muito melhor acessar o código-fonte do ifconfig e obter a funcionalidade do que executá-lo e analisar sua saída.
Zaur Nasibov
1
@MartinMeeser: tornou seu idioma neutro, procurando por "inet" e depois por ":" separadamente.
Erik Aronesty
1
Então você tem outros idiomas que nem mesmo são conjuntos de caracteres baseados em latim. A solução é não usar ferramentas de comando, pois elas estão obsoletas ifconfig. Você deve usar ipagora. A maneira adequada de gerar uma saída de linguagem neutra é definir LANGa variável de ambiente Ccomo:LANG=C ls -l
Anders
1
Obrigado. É uma solução fácil e incrível!
Ahmad Afkandeh
5

Eu não acho que haja uma resposta certa definitiva para sua pergunta. Em vez disso, há uma grande variedade de maneiras de chegar perto do que você deseja. Portanto, irei fornecer algumas dicas de como fazer isso.

Se uma máquina tiver mais de 2 interfaces ( loconta como uma), você terá problemas para detectar automaticamente a interface certa com facilidade. Aqui estão algumas receitas de como fazer isso.

O problema, por exemplo, é se os hosts estão em uma DMZ atrás de um firewall NAT que muda o IP público para algum IP privado e encaminha as solicitações. Sua máquina pode ter 10 interfaces, mas apenas uma corresponde à pública.

Mesmo a autodetecção não funciona no caso de você estar em duplo-NAT, onde seu firewall até mesmo traduz o IP de origem em algo completamente diferente. Portanto, você não pode ter certeza de que a rota padrão leva à sua interface com uma interface pública.

Detecte-o pela rota padrão

Esta é a minha maneira recomendada de autodetectar coisas

Algo como ip r get 1.1.1.1geralmente informa a interface que tem a rota padrão.

Se você quiser recriar isso em sua linguagem de script / programação favorita, use strace ip r get 1.1.1.1e siga o caminho dos tijolos amarelos.

Definir com /etc/hosts

Esta é minha recomendação se você quiser ficar no controle

Você pode criar uma entrada /etc/hostscomo

80.190.1.3 publicinterfaceip

Então você pode usar este alias publicinterfaceippara se referir à sua interface pública.

Infelizmente haproxynão sabe esse truque com o IPv6

Use o ambiente

Esta é uma boa solução para o /etc/hostscaso de você não estarroot

O mesmo que /etc/hosts. mas use o ambiente para isso. Você pode tentar /etc/profileou ~/.profilepor isso.

Portanto, se seu programa precisa de uma variável MYPUBLICIP, você pode incluir código como (este é C, fique à vontade para criar C ++ a partir dele):

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

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

Então você pode chamar seu script / programa /path/to/your/scriptassim

MYPUBLICIP=80.190.1.3 /path/to/your/script

isso até funciona em crontab.

Enumere todas as interfaces e elimine aquelas que você não deseja

A maneira desesperada se você não pode usar ip

Se você sabe o que não quer, pode enumerar todas as interfaces e ignorar todas as falsas.

Aqui já parece haver uma resposta https://stackoverflow.com/a/265978/490291 para essa abordagem.

Faça como DLNA

O jeito do bêbado que tenta se afogar no álcool

Você pode tentar enumerar todos os gateways UPnP em sua rede e, dessa forma, descobrir uma rota adequada para alguma coisa "externa". Isso pode até ser em uma rota para a qual sua rota padrão não aponta.

Para mais informações, talvez consulte https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

Isso dá a você uma boa impressão de qual é a sua interface pública real, mesmo se sua rota padrão apontar para outro lugar.

Tem ainda mais

Onde a montanha encontra o profeta

Os roteadores IPv6 se anunciam para fornecer o prefixo IPv6 correto. Olhar para o prefixo dá uma dica sobre se ele tem algum IP interno ou global.

Você pode escutar os quadros IGMP ou IBGP para descobrir algum gateway adequado.

Existem menos de 2 ^ 32 endereços IP. Conseqüentemente, em uma LAN não demora muito para fazer ping em todos eles. Isso lhe dá uma dica estatística sobre onde a maior parte da Internet está localizada do seu ponto de vista. No entanto, você deve ser um pouco mais sensato do que o famoso https://de.wikipedia.org/wiki/SQL_Slammer

ICMP e até ARP são boas fontes de informações de banda lateral de rede. Também pode te ajudar.

Você pode usar o endereço de Broadcast Ethernet para entrar em contato com todos os seus dispositivos de infraestrutura de rede, o que geralmente ajudará, como DHCP (mesmo DHCPv6) e assim por diante.

Essa lista adicional é provavelmente interminável e sempre incompleta, porque todos os fabricantes de dispositivos de rede estão ocupados inventando novas brechas de segurança para detectar automaticamente seus próprios dispositivos. O que geralmente ajuda muito a detectar alguma interface pública onde não deveria haver uma.

'Nuff disse. Fora.

Tino
fonte
4

Além do que Steve Baker disse, você pode encontrar uma descrição do SIOCGIFCONF ioctl na página do manual netdevice (7) .

Depois de ter a lista de todos os endereços IP no host, você terá que usar a lógica específica do aplicativo para filtrar os endereços que não deseja e esperar que ainda tenha um endereço IP.

camh
fonte
4

Não fixe o código: esse é o tipo de coisa que pode mudar. Muitos programas descobrem o que vincular lendo um arquivo de configuração e fazendo o que quer que ele diga. Dessa forma, se no futuro seu programa precisar se vincular a algo que não seja um IP público, ele poderá fazer isso.

Thanatos
fonte
2

Como a pergunta especifica o Linux, minha técnica favorita para descobrir os endereços IP de uma máquina é usar o netlink. Ao criar um soquete netlink do protocolo NETLINK_ROUTE e enviar um RTM_GETADDR, seu aplicativo receberá mensagem (ns) contendo todos os endereços IP disponíveis. Um exemplo é fornecido aqui .

Para simplificar partes do tratamento da mensagem, libmnl é conveniente. Se você está curioso em saber mais sobre as diferentes opções do NETLINK_ROUTE (e como são analisadas), a melhor fonte é o código-fonte do iproute2 (especialmente o aplicativo do monitor), bem como as funções de recepção no kernel. A página de manual do rtnetlink também contém informações úteis.

Kristian Evensen
fonte
1

Você pode fazer alguma integração com curl tão fácil como: curl www.whatismyip.orgdo shell você obterá seu ip global. Você meio que depende de algum servidor externo, mas sempre dependerá se estiver atrás de um NAT.


fonte
Eu nunca entendo porque mais pessoas não usam este site. Eles até têm uma página legível por máquina para que você não precise filtrar as informações.
deft_code
1
Você pode não obter o IP correto se estiver operando dentro do proxy.
Jack
1
icanhazip.com retorna o IP e nada mais. Perfeito para incorporar em um script bash!
lukecyca
ipinfo.io/ip é outra alternativa. Se você curl ipinfo.io, ele também retorna o nome do host, informações de geolocalização e muito mais no formato 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
fonte
10
Esta é uma solução pesada, apenas para Windows (questão sobre o Linux), mal formatada, que depende de serviços fora do controle local por meio da análise frágil do conteúdo do site. Não consigo encontrar nenhum lado positivo desta "solução".
viraptor
1
Hah! A análise de HTML até procura um comentário que é um aviso sobre exatamente o que está sendo feito.
notlesh 01 de