¿Puede obtener la dirección IP de la LAN local de un usuario a través de JavaScript?

102

Sé que la reacción inicial a esta pregunta es "no" y "no se puede hacer" y "no debería necesitarlo, está haciendo algo mal". Lo que estoy tratando de hacer es obtener la dirección IP de la LAN de los usuarios y mostrarla en la página web. ¿Por qué? Porque de eso se trata la página en la que estoy trabajando, que muestra la mayor cantidad de información posible sobre usted, el visitante: http://www.whatsmyip.org/more-info-about-you/

Así que en realidad no estoy HACIENDO nada con la IP, más que mostrársela al usuario con fines informativos. Solía ​​hacer esto usando un pequeño subprograma de Java. Funcionó bastante bien. Pero en estos días, el navegador te hace presionar de acuerdo y confiar tantas veces, para ejecutar incluso el subprograma de Java más pequeño, que prefiero no ejecutar uno en absoluto.

Así que por un tiempo me deshice de esta función, pero me gustaría recuperarla si es posible. Era algo que yo, como consultor informático, usaba de vez en cuando. Es más rápido ir a este sitio web para ver en qué rango de IP se está ejecutando una red, que ir a Preferencias del sistema, Redes y luego cualquier interfaz que esté activa.

Así que me pregunto, con la esperanza, si hay alguna forma de hacerlo solo en javascript. Tal vez algún objeto nuevo al que pueda acceder, similar a la forma en que JavaScript puede preguntarle al navegador dónde está la ubicación geográfica en la tierra. ¿Quizás hay algo similar para la información de redes de clientes? Si no es así, ¿quizás hay alguna otra forma de hacerlo? Las únicas formas en las que puedo pensar son en un applet de Java o un objeto flash. Prefiero no hacer ninguna de esas.

l008com
fuente
1
Tu sabes la respuesta. ¿Por qué preguntar entonces? Es poco probable que los usuarios permitan subprogramas de Java u objetos flash (puede que solo lo hagan aquellos que son nuevos en Internet), por lo que no es una solución en casos comunes. ActiveX y las cosas cercanas funcionan solo en IE y, por lo tanto, los usuarios de otros navegadores no se verán afectados (y, más aún, incluso en IE hay una política de seguridad que evita que el sitio web haga cosas desagradables)
Alma Do
Mi dirección IP se captura a través HTTP_X_FORWARDED_FORde esa página, solo digo '.
tomdemuyt
50
¿Por qué preguntar entonces? Porque tal vez, solo tal vez, no lo sé todo.
l008com
1
Estos chicos lo hacen: whatismyproxy.com
likebike
1
@likebike Bonito. Investigando cómo lo están haciendo.
Dominic Cerisano

Respuestas:

117

Resulta que la reciente extensión WebRTC de HTML5 permite a JavaScript consultar la dirección IP del cliente local. Una prueba de concepto está disponible aquí: http://net.ipcalf.com

Esta característica aparentemente es por diseño y no es un error. Sin embargo, dada su naturaleza controvertida, sería cauteloso al confiar en este comportamiento. Sin embargo, creo que se dirige perfecta y adecuadamente a su propósito previsto (revelar al usuario qué está filtrando su navegador).

afourney
fuente
1
Esto fue útil. ¡Gracias de nuevo!
Ansuraj Khadanga
7
Sólo está trabajando en el Chrome y Firefox, y no en el IE, Edge o Safari
ali
Estaba buscando mi IP WAN y este sitio web whatismyip.com también me dio mi IP local y supongo que tenía algo que ver con JS.
Shayan
@ali Tienes razón, el sitio web que mencioné anteriormente no puede decir mi IP local en Edge.
Shayan
6
Google Chrome oculta la IP local de forma predeterminada. Muestra algo similar a e87e041d-15e1-4662-adad-7a6601fca9fb.local . Este comportamiento se puede cambiar configurando la variable # enable-webrtc-hide-local-ips-with-mdns en deshabilitado en Chrome: // flags
injaon
81

Actualizar

Esta solución ya no funcionaría porque los navegadores están arreglando la fuga de webrtc: para obtener más información al respecto, lea esta otra pregunta: RTCIceCandidate ya no devuelve IP


Además de la respuesta de afourney, este código funciona en navegadores compatibles con WebRTC (Chrome y Firefox). Escuché que se está llevando a cabo un movimiento para implementar una función que hace que los sitios soliciten la IP (como en el caso de la ubicación geográfica del usuario o los medios del usuario), aunque aún no se ha implementado en ninguno de esos navegadores.

Aquí hay una versión modificada del código fuente , redujo las líneas, sin hacer ninguna solicitud de aturdimiento ya que solo desea la IP local, no la IP pública:

window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;//compatibility for Firefox and chrome
var pc = new RTCPeerConnection({iceServers:[]}), noop = function(){};      
pc.createDataChannel('');//create a bogus data channel
pc.createOffer(pc.setLocalDescription.bind(pc), noop);// create offer and set local description
pc.onicecandidate = function(ice)
{
 if (ice && ice.candidate && ice.candidate.candidate)
 {
  var myIP = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/.exec(ice.candidate.candidate)[1];
  console.log('my IP: ', myIP);   
  pc.onicecandidate = noop;
 }
};

Estamos creando una conexión de par ficticia para que el par remoto se comunique con nosotros. Generalmente intercambiamos candidatos de hielo entre nosotros y al leer los candidatos de hielo podemos decirle la ip del usuario.

Puede encontrar una demostración en -> Demo

mido
fuente
¡Gracias por esto Mido! Muy apreciado.
Sujay Phadke
1
@dampee: creo que Edge no admite canales de datos en este momento.
MichaelB76
¿Qué es un canal de datos falso? No puedo encontrar ninguna referencia en Google
AmazingTurtle
2
tenga en cuenta que la api createOffer ha cambiado para basarse en Promise en lugar de successCallback y failCallback como parámetros, por lo que es posible que esto no funcione en versiones más nuevas, consulte: developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/…
Dickeylth
10

La API de WebRTC se puede utilizar para recuperar la IP local del cliente.

Sin embargo, es posible que el navegador no lo admita o que el cliente lo haya desactivado por razones de seguridad. En cualquier caso, no se debe confiar en este "truco" a largo plazo, ya que es probable que se repare en el futuro (consulte la respuesta de Cullen Fluffy Jennings).

El código ECMAScript 6 a continuación demuestra cómo hacerlo.

/* ES6 */
const findLocalIp = (logInfo = true) => new Promise( (resolve, reject) => {
    window.RTCPeerConnection = window.RTCPeerConnection 
                            || window.mozRTCPeerConnection 
                            || window.webkitRTCPeerConnection;

    if ( typeof window.RTCPeerConnection == 'undefined' )
        return reject('WebRTC not supported by browser');

    let pc = new RTCPeerConnection();
    let ips = [];

    pc.createDataChannel("");
    pc.createOffer()
     .then(offer => pc.setLocalDescription(offer))
     .catch(err => reject(err));
    pc.onicecandidate = event => {
        if ( !event || !event.candidate ) {
            // All ICE candidates have been sent.
            if ( ips.length == 0 )
                return reject('WebRTC disabled or restricted by browser');

            return resolve(ips);
        }

        let parts = event.candidate.candidate.split(' ');
        let [base,componentId,protocol,priority,ip,port,,type,...attr] = parts;
        let component = ['rtp', 'rtpc'];

        if ( ! ips.some(e => e == ip) )
            ips.push(ip);

        if ( ! logInfo )
            return;

        console.log(" candidate: " + base.split(':')[1]);
        console.log(" component: " + component[componentId - 1]);
        console.log("  protocol: " + protocol);
        console.log("  priority: " + priority);
        console.log("        ip: " + ip);
        console.log("      port: " + port);
        console.log("      type: " + type);

        if ( attr.length ) {
            console.log("attributes: ");
            for(let i = 0; i < attr.length; i += 2)
                console.log("> " + attr[i] + ": " + attr[i+1]);
        }

        console.log();
    };
} );

Observe que escribo return resolve(..)o return reject(..)como atajo. Ambas funciones no devuelven nada.

Entonces puede tener algo como esto:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Local IP</title>
</head>
<body>
    <h1>My local IP is</h1>
    <p id="ip">Loading..</p>
    <script src="ip.js"></script>
    <script>
    let p = document.getElementById('ip');
    findLocalIp().then(
        ips => {
            let s = '';
            ips.forEach( ip => s += ip + '<br>' );
            p.innerHTML = s;
        },
        err => p.innerHTML = err
    );
    </script>
</body>
</html>
Linblow
fuente
9

Limpié la publicación de mido y luego limpié la función que encontraron. Esto volverá falseo un array. Al realizar la prueba, recuerde que debe contraer la matriz en la consola del desarrollador web, de lo contrario, su comportamiento predeterminado no intuitivo puede engañarlo y hacerle pensar que está devolviendo un vacío array.

function ip_local()
{
 var ip = false;
 window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || false;

 if (window.RTCPeerConnection)
 {
  ip = [];
  var pc = new RTCPeerConnection({iceServers:[]}), noop = function(){};
  pc.createDataChannel('');
  pc.createOffer(pc.setLocalDescription.bind(pc), noop);

  pc.onicecandidate = function(event)
  {
   if (event && event.candidate && event.candidate.candidate)
   {
    var s = event.candidate.candidate.split('\n');
    ip.push(s[0].split(' ')[4]);
   }
  }
 }

 return ip;
}

Además, tenga en cuenta que esto no es algo antiguo como CSS, border-radiusaunque es uno de esos bits que no son compatibles con IE11 y versiones anteriores. Utilice siempre la detección de objetos, pruebe en navegadores razonablemente más antiguos (por ejemplo, Firefox 4, IE9, Opera 12.1) y asegúrese de que sus scripts más nuevos no rompan los bits de código más nuevos. Además, siempre detecte primero el código compatible con los estándares, por lo que si hay algo con, por ejemplo, un prefijo CSS, detecte primero el código estándar sin prefijo y luego retroceda, ya que a largo plazo el soporte eventualmente se estandarizará por el resto de su existencia.

Juan
fuente
estás redeclarando ip- línea 3 y línea 8.
user2757813
@Anu WebRTC no se introdujo hasta Internet Explorer 15 (o "Edge 15"), así que no. Es por eso que en la cuarta línea anterior, si ninguno de los objetos existe, la función devolverá falso. Si hay otra forma de lograr esto en IE, entonces no soy consciente de ello en este momento.
John
@John - ¿cómo pasamos el valor de retorno a una variable php? ¿A través de una publicación oculta?
MarcoZen
@MarcoZen Puede usar <input name="example1" type="hidden" value="whatever" />o usar un POST AJAX en tal situación. Recomiendo encarecidamente estudiar mi ajax()función aquí: jabcreations.com/docs/javascript
John
Acabo de descubrir que algunos navegadores (por ejemplo, Chrome) ahora bloquean el suministro de la IP; el mismo código ahora se resuelve en un nombre de host mDNS, si no se solicita permiso de video / audio. Ver groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU
Christoph Bimminger
6

function getUserIP(onNewIP) { //  onNewIp - your listener function for new IPs
  //compatibility for firefox and chrome
  var myPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
  var pc = new myPeerConnection({
      iceServers: []
    }),
    noop = function() {},
    localIPs = {},
    ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g,
    key;

  function iterateIP(ip) {
    if (!localIPs[ip]) onNewIP(ip);
    localIPs[ip] = true;
  }
  onNewIP
  //create a bogus data channel
  pc.createDataChannel("");

  // create offer and set local description
  pc.createOffer().then(function(sdp) {
    sdp.sdp.split('\n').forEach(function(line) {
      if (line.indexOf('candidate') < 0) return;
      line.match(ipRegex).forEach(iterateIP);
    });

    pc.setLocalDescription(sdp, noop, noop);
  }).catch(function(reason) {
    // An error occurred, so handle the failure to connect
  });

  //listen for candidate events
  pc.onicecandidate = function(ice) {
    if (!ice || !ice.candidate || !ice.candidate.candidate || !ice.candidate.candidate.match(ipRegex)) return;
    ice.candidate.candidate.match(ipRegex).forEach(iterateIP);
  };
}
getUserIP(console.log)

Joaquín Piñeyro
fuente
Utilice las opciones del editor para formatear su código de forma adecuada.
31piy
3
Sería genial si no solo soltaras un código, sino que también dieras una explicación de lo que está sucediendo en él y en el tuyo. Ayuda al autor de la pregunta y a otros usuarios. Está bien si funciona, pero saber por qué es aún más importante en mi opinión.
davejal
¿Alguna solución compatible con IE?
Anu
1
El comentario es una copia y pegado de este artículo: ourcodeworld.com/articles/read/257/…
Darkshifty
Acabo de descubrir que algunos navegadores (por ejemplo, Chrome) ahora bloquean el suministro de la IP; el mismo código ahora se resuelve en un nombre de host mDNS, si no se solicita permiso de video / audio. Ver groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU
Christoph Bimminger
5

Chrome 76+

El año pasado utilicé la respuesta de Linblow (2018-Oct-19) para descubrir con éxito mi IP local a través de javascript. Sin embargo, las actualizaciones recientes de Chrome (¿76?) Se han equivocado sobre este método, por lo que ahora devuelve una IP ofuscada, como:1f4712db-ea17-4bcf-a596-105139dfd8bf.local

Si tiene control total sobre su navegador, puede deshacer este comportamiento desactivándolo en Chrome Flags, escribiendo esto en su barra de direcciones:

chrome://flags

y DESHABILITANDO la bandera Anonymize local IPs exposed by WebRTC

En mi caso, necesito la IP de un script de TamperMonkey para determinar mi ubicación actual y hacer cosas diferentes según mi ubicación. También tengo control total sobre la configuración de mi propio navegador (sin políticas corporativas, etc.). Entonces, para mí, cambiar la chrome://flagsconfiguración funciona.

Fuentes:

https://groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU

https://codelabs.developers.google.com/codelabs/webrtc-web/index.html

cssyphus
fuente
esa bandera podría desaparecer. Actualmente, parece que las extensiones aún obtienen direcciones IP, por lo que puede intentar obtenerlas desde el script en segundo plano. Sin embargo, a largo plazo, todas las apuestas están canceladas.
Philipp Hancke
1
Según groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU , la IP aún debería devolverse si implementas tu solución que solicita permiso de audio / video.
Christoph Bimminger
4

Puede encontrar más información sobre las limitaciones que probablemente agregarán los navegadores para mitigar esto y qué está haciendo el IETF al respecto, así como por qué es necesario en IETF SPEC sobre el manejo de IP

Jennings esponjoso de Cullen
fuente
0

Se RTCPeerConnectionpuede utilizar una. En navegadores como Chrome donde getUserMediase requiere un permiso , podemos simplemente detectar los dispositivos de entrada disponibles y solicitarlos.

const internalIp = async () => {
    if (!RTCPeerConnection) {
        throw new Error("Not supported.")
    }

    const peerConnection = new RTCPeerConnection({ iceServers: [] })

    peerConnection.createDataChannel('')
    peerConnection.createOffer(peerConnection.setLocalDescription.bind(peerConnection), () => { })

    peerConnection.addEventListener("icecandidateerror", (event) => {
        throw new Error(event.errorText)
    })

    return new Promise(async resolve => {
        peerConnection.addEventListener("icecandidate", async ({candidate}) => {
            peerConnection.close()

            if (candidate && candidate.candidate) {
                const result = candidate.candidate.split(" ")[4]
                if (result.endsWith(".local")) {
                    const inputDevices = await navigator.mediaDevices.enumerateDevices()
                    const inputDeviceTypes = inputDevices.map(({ kind }) => kind)

                    const constraints = {}

                    if (inputDeviceTypes.includes("audioinput")) {
                        constraints.audio = true
                    } else if (inputDeviceTypes.includes("videoinput")) {
                        constraints.video = true
                    } else {
                        throw new Error("An audio or video input device is required!")
                    }

                    const mediaStream = await navigator.mediaDevices.getUserMedia(constraints)
                    mediaStream.getTracks().forEach(track => track.stop())
                    resolve(internalIp())
                }
                resolve(result)
            }
        })
    })
}
Richie Bendall
fuente