Obtenga la dirección IP de la máquina

94

Esta pregunta es casi la misma que la pregunta anterior Obtener la dirección IP de la computadora local . Sin embargo, necesito encontrar la (s) dirección (es) IP de una máquina Linux .

Entonces: ¿Cómo puedo, programáticamente en C ++ , detectar las direcciones IP del servidor Linux en el que se está ejecutando mi aplicación? Los servidores tendrán al menos dos direcciones IP y necesito una específica (la de una red determinada (la pública)).

Estoy seguro de que hay una función simple para hacer eso, pero ¿dónde?


Para aclarar un poco las cosas:

  • El servidor obviamente tendrá el "localhost": 127.0.0.1
  • El servidor tendrá una dirección IP interna (de gestión): 172.16.xx
  • El servidor tendrá una dirección IP externa (pública): 80.190.xx

Necesito encontrar la dirección IP externa para vincular mi aplicación a ella. Obviamente, también puedo vincularme a INADDR_ANY (y en realidad eso es lo que hago en este momento). Sin embargo, preferiría detectar la dirección pública.

Culpa
fuente
3
¿Por qué marcar el popen de ifconfig? Realmente es lo correcto. Las bibliotecas cambian, pero ifconfig y popen siempre estarán ahí.
Erik Aronesty
3
No, ifconfig, routeetc están en desuso para el ipcomando. Ahora deberías usar eso en su lugar.
Anders
ifconfig todavía, hoy, funciona en más arquitecturas que cualquier otro enfoque. Así que cámbielo por el comando ip. Cuando / si corresponde. popen sigue siendo la mejor solución.
Erik Aronesty

Respuestas:

104

Encontré la solución ioctl problemática en os x (que es compatible con POSIX, por lo que debería ser similar a linux). Sin embargo, getifaddress () le permitirá hacer lo mismo fácilmente, funciona bien para mí en os x 10.5 y debería ser el mismo a continuación.

He hecho un ejemplo rápido a continuación que imprimirá todas las direcciones IPv4 de la máquina (también debe verificar que getifaddrs fue exitoso, es decir, devuelve 0).

Lo actualicé para mostrar direcciones IPv6 también.

#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;
}
Doce47
fuente
7
+1 y gracias por traer este buen enfoque en lugar de esos aburridos ioctl. Sin embargo, su manejo de IP6 sigue siendo incorrecto; debe lanzar a sockaddr_in6, es decir, algo comotmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
Andrey
2
@Andrey, gracias. He actualizado mi respuesta (aunque no he tenido la oportunidad de probarla)
Doce47
2
da loe wlan0interfaces en mi caso. Lo que necesito es wlan0o eth0que tenga conexión a Internet. Entonces, ¿debo usar strcmpo hay una mejor manera?
Sudip Bhattarai
28
  1. Crea un enchufe.
  2. Realizar ioctl(<socketfd>, SIOCGIFCONF, (struct ifconf)&buffer);

Lea /usr/include/linux/if.hpara obtener información sobre las estructuras ifconfy ifreq. Esto debería darle la dirección IP de cada interfaz en el sistema. Lea también /usr/include/linux/sockios.hpara ioctls adicionales.

Steve Baker
fuente
Tenía la impresión de que solo le dará la dirección de la interfaz a la que se une.
Matt Joiner
@MattJoiner No, eche un vistazo a la página del manual, SIOCGIFCONF devuelve información sobre todas las interfaces. Parte de la página de manual citada aquí: "Devuelve una lista de direcciones de interfaz (capa de transporte). Esto actualmente significa solo direcciones de la familia AF_INET (IPv4) para compatibilidad. El usuario pasa una estructura ifconf como argumento a ioctl. Contiene un puntero a una matriz de estructuras ifreq en ifc_req y su longitud en bytes en ifc_len. El kernel llena los ifreqs con todas las direcciones de interfaz L3 actuales que se están ejecutando "
Stéphane
25

Me gusta la respuesta de jjvainio . Como dice Zan Lnyx, usa la tabla de enrutamiento local para encontrar la dirección IP de la interfaz Ethernet que se usaría para una conexión a un host externo específico. Al utilizar un conector UDP conectado, puede obtener la información sin enviar ningún paquete. El enfoque requiere que elija un host externo específico. La mayoría de las veces, cualquier IP pública conocida debería ser suficiente. Me gusta la dirección 8.8.8.8 del servidor DNS público de Google para este propósito, pero puede haber ocasiones en las que desee elegir una IP de host externa diferente. Aquí hay un código que ilustra el enfoque completo.

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
fuente
2
esto me da ip privada 127. *. *. *. tal vez porque estoy detrás de NAT?
eugene
14

Como ha descubierto, no existe una única "dirección IP local". A continuación, le indicamos cómo averiguar la dirección local que se puede enviar a un host específico.

  1. Crea un socket UDP
  2. Conecte el enchufe a una dirección externa (el host que eventualmente recibirá la dirección local)
  3. Use getsockname para obtener la dirección local
jjvainio
fuente
Nunca lo he hecho de esta manera pero me gusta. Utiliza la tabla de enrutamiento del sistema operativo automáticamente y es mucho más fácil que escanear la lista de interfaces.
Zan Lynx
5
Esto supone que sabe cuál será una dirección externa. A menudo, este no es el caso en los centros de datos.
Christopher
Siempre puede probar tres que estén asignados a diferentes continentes (IANA lo hace fácil) y aceptar los mejores dos de tres.
Joshua
9

Esto tiene la ventaja de trabajar en muchas versiones de Unix ... y puedes modificarlo trivialmente para que funcione en cualquier sistema operativo. Todas las soluciones anteriores me dan errores de compilación según la fase de la luna. En el momento en que haya una buena forma POSIX de hacerlo ... no use esto (en el momento en que se escribió esto, ese no era el 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
fuente
gracias por esta solución, funciona muy bien para mí y es muy útil en muchas situaciones cuando solo desea obtener algunos datos de los programas cl
zitroneneis
Aunque ifconfig genera los datos requeridos, desaconsejaría enfáticamente el uso de cualquier programa de esta manera. Es mucho mejor entrar en el código fuente de ifconfig y obtener la funcionalidad que ejecutarlo y analizar su salida.
Zaur Nasibov
1
@MartinMeeser: hizo que el lenguaje fuera neutral, buscando "inet" y luego ":" por separado.
Erik Aronesty
1
Luego tienes otros idiomas que ni siquiera son juegos de caracteres basados ​​en latín. La solución es no usar herramientas de comando, ya que quedan obsoletas como ifconfig. Deberías usar ipahora. La forma correcta de generar una salida de lenguaje neutral es establecer LANGla variable de entorno en C, como:LANG=C ls -l
Anders
1
Gracias. ¡Es una solución fácil e increíble!
Ahmad Afkandeh
5

No creo que haya una respuesta correcta definitiva a su pregunta. En cambio, hay un gran conjunto de formas de acercarse a lo que desea. Por lo tanto, proporcionaré algunos consejos sobre cómo hacerlo.

Si una máquina tiene más de 2 interfaces ( locuenta como una), tendrá problemas para detectar automáticamente la interfaz correcta fácilmente. Aquí hay algunas recetas sobre cómo hacerlo.

El problema, por ejemplo, es si los hosts están en una DMZ detrás de un firewall NAT que cambia la IP pública a alguna IP privada y reenvía las solicitudes. Su máquina puede tener 10 interfaces, pero solo una corresponde a la pública.

Incluso la autodetección no funciona en caso de que esté en doble NAT, donde su firewall incluso traduce la IP de origen en algo completamente diferente. Por lo tanto, ni siquiera puede estar seguro de que la ruta predeterminada conduce a su interfaz con una interfaz pública.

Detectarlo por la ruta predeterminada

Esta es mi forma recomendada de autodetectar cosas

Algo como ip r get 1.1.1.1normalmente te dice la interfaz que tiene la ruta predeterminada.

Si desea recrear esto en su lenguaje de programación / scripts favorito, use strace ip r get 1.1.1.1y siga el camino de ladrillos amarillos.

Configurarlo con /etc/hosts

Esta es mi recomendación si quieres mantener el control

Puedes crear una entrada /etc/hostscomo

80.190.1.3 publicinterfaceip

Entonces puede usar este alias publicinterfaceippara referirse a su interfaz pública.

Lamentablemente haproxyno asimila este truco con IPv6

Usa el medio ambiente

Esta es una buena solución /etc/hostsen caso de que noroot

Igual que /etc/hosts. pero usa el entorno para esto. Puedes probar /etc/profileo ~/.profilepara esto.

Por lo tanto, si su programa necesita una variable MYPUBLICIP, puede incluir código como (esto es C, no dude en crear C ++ a partir de él):

#define MYPUBLICIPENVVAR "MYPUBLICIP"

const char *mypublicip = getenv(MYPUBLICIPENVVAR);

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

Entonces puedes llamar a tu script / programa /path/to/your/scriptasí

MYPUBLICIP=80.190.1.3 /path/to/your/script

esto incluso funciona en crontab.

Enumere todas las interfaces y elimine las que no desee

La forma desesperada si no puedes usar ip

Si sabe lo que no desea, puede enumerar todas las interfaces e ignorar todas las falsas.

Aquí ya parece haber una respuesta https://stackoverflow.com/a/265978/490291 para este enfoque.

Hazlo como DLNA

El camino del borracho que intenta ahogarse en alcohol

Puede intentar enumerar todas las puertas de enlace UPnP en su red y de esta manera encontrar una ruta adecuada para alguna cosa "externa". Esto incluso podría estar en una ruta donde su ruta predeterminada no apunta.

Para obtener más información sobre esto, quizás consulte https://en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol

Esto le da una buena impresión de cuál es su interfaz pública real, incluso si su ruta predeterminada apunta a otro lugar.

Hay aun mas

Donde la montaña se encuentra con el profeta

Los enrutadores IPv6 se anuncian a sí mismos para ofrecerle el prefijo IPv6 correcto. Mirar el prefijo le da una pista sobre si tiene alguna IP interna o global.

Puede escuchar tramas IGMP o IBGP para encontrar una puerta de enlace adecuada.

Hay menos de 2 ^ 32 direcciones IP. Por lo tanto, no se necesita mucho tiempo en una LAN para hacer ping a todos. Esto le da una pista estadística sobre dónde se encuentra la mayoría de Internet desde su punto de vista. Sin embargo, debería ser un poco más sensato que el famoso https://de.wikipedia.org/wiki/SQL_Slammer

ICMP e incluso ARP son buenas fuentes de información de banda lateral de la red. También podría ayudarte.

Puede usar la dirección de transmisión Ethernet para comunicarse con todos los dispositivos de su infraestructura de red, lo que a menudo le ayudará, como DHCP (incluso DHCPv6), etc.

Esta lista adicional probablemente sea interminable y siempre incompleta, porque todos los fabricantes de dispositivos de red están inventando afanosamente nuevos agujeros de seguridad sobre cómo detectar automáticamente sus propios dispositivos. Lo que a menudo ayuda mucho sobre cómo detectar alguna interfaz pública donde no debería haber una.

'Nuff dijo. Fuera.

Tino
fuente
4

Además de lo que ha dicho Steve Baker, puede encontrar una descripción del ioctl SIOCGIFCONF en la página de manual de netdevice (7) .

Una vez que tenga la lista de todas las direcciones IP en el host, tendrá que usar la lógica específica de la aplicación para filtrar las direcciones que no desea y esperar que le quede una dirección IP.

camh
fuente
4

No lo codifique: este es el tipo de cosas que pueden cambiar. Muchos programas descubren a qué vincularse leyendo un archivo de configuración y haciendo lo que sea que indique. De esta forma, si su programa en el futuro necesita vincularse a algo que no es una IP pública, puede hacerlo.

Thanatos
fuente
2

Como la pregunta especifica Linux, mi técnica favorita para descubrir las direcciones IP de una máquina es usar netlink. Al crear un conector de enlace de red del protocolo NETLINK_ROUTE y enviar un RTM_GETADDR, su aplicación recibirá un mensaje que contiene todas las direcciones IP disponibles. Aquí se proporciona un ejemplo .

Para simplemente partes del manejo de mensajes, libmnl es conveniente. Si tiene curiosidad por averiguar más sobre las diferentes opciones de NETLINK_ROUTE (y cómo se analizan), la mejor fuente es el código fuente de iproute2 (especialmente la aplicación de monitorización), así como las funciones de recepción en el kernel. La página de manual de rtnetlink también contiene información útil.

Kristian Evensen
fuente
1

Puede hacer algo de integración con curl como algo tan fácil como: curl www.whatismyip.orgdesde el shell obtendrá su ip global. Depende de algún servidor externo, pero siempre lo será si está detrás de un NAT.


fuente
Nunca entiendo por qué más personas no usan este sitio. Incluso tienen una página legible por máquina para que no tenga que filtrar la información.
deft_code
1
Es posible que no obtenga la IP correcta si está operando dentro de un proxy.
Jack
1
icanhazip.com devuelve la IP y nada más. ¡Perfecto para incrustar en un script bash!
lukecyca
ipinfo.io/ip es otra alternativa. Si curl ipinfo.io , también devuelve el nombre de host, la información de geolocalización y más en 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
fuente
10
Esta es una solución pesada, solo para Windows (pregunta sobre Linux), mal formateada que se basa en servicios fuera del control local a través de un frágil análisis del contenido del sitio web. No puedo encontrar ningún lado positivo de esta "solución".
viraptor
1
¡Ja! El análisis de HTML incluso busca un comentario que sea una advertencia sobre lo que se está haciendo exactamente.
notlesh