Você pode obter o endereço IP da LAN local de um usuário via JavaScript?

102

Eu sei que a reação inicial a esta pergunta é "não" e "não pode ser feito" e "você não deveria precisar, você está fazendo algo errado". O que estou tentando fazer é obter o endereço IP da LAN dos usuários e exibi-lo na página da web. Por quê? Porque é disso que se trata a página em que estou trabalhando, mostrando o máximo de informações possível sobre você, o visitante: http://www.whatsmyip.org/more-info-about-you/

Portanto, não estou FAZENDO nada com o IP, a não ser mostrá-lo ao usuário para fins informativos. Eu costumava fazer isso usando um pequeno miniaplicativo Java. Funcionou muito bem. Mas hoje em dia, o navegador faz com que você concorde e confie tantas vezes, para executar até mesmo o menor miniaplicativo Java, que prefiro não executar nenhum.

Então, por um tempo, acabei de me livrar desse recurso, mas gostaria que ele voltasse, se possível. Era algo que eu, como consultor de informática, usaria de vez em quando. É mais rápido ir a este site para ver em qual intervalo de IP uma rede está sendo executada, do que ir em Preferências do Sistema, Rede e, em seguida, qualquer interface que esteja ativa.

Portanto, espero que haja alguma maneira de fazer isso apenas em javascript? Talvez algum novo objeto que você possa acessar, semelhante ao modo como o javascript pode perguntar ao navegador onde está a localização geográfica na Terra. Talvez haja algo semelhante para informações de rede do cliente? Se não, talvez haja alguma outra maneira inteiramente de fazer isso? As únicas maneiras que consigo pensar são em um miniaplicativo java ou um objeto flash. Prefiro não fazer nada disso.

l008com
fonte
1
Você sabe a resposta. Por que perguntar então? É improvável que applets Java ou objetos flash sejam permitidos pelos usuários (pode ser apenas por aqueles que são novos na Internet) - portanto, não é uma solução no caso comum. ActiveX e coisas próximas estão funcionando apenas no IE - e, portanto, os usuários de outros navegadores não serão afetados (e, mais, mesmo no IE há uma política de segurança que impede o site de fazer coisas desagradáveis)
Alma Do
Meu endereço IP é capturado HTTP_X_FORWARDED_FORnessa página, basta dizer.
tomdemuyt
50
Por que perguntar então? Porque talvez, apenas talvez, eu não saiba tudo.
l008com
1
Esses caras fazem isso: whatismyproxy.com
likebike
1
@likebike Legal. Olhando para como eles estão fazendo isso.
Dominic Cerisano

Respostas:

117

Acontece que a recente extensão WebRTC do HTML5 permite que o javascript consulte o endereço IP do cliente local. Uma prova de conceito está disponível aqui: http://net.ipcalf.com

Esse recurso é aparentemente por design e não é um bug. No entanto, devido à sua natureza controversa, eu seria cauteloso ao confiar nesse comportamento. No entanto, acho que ele atende de forma perfeita e apropriada ao objetivo pretendido (revelar ao usuário o que o navegador está vazando).

uma viagem
fonte
1
Isso foi útil. Obrigado novamente!
Ansuraj Khadanga
7
Ele está apenas trabalhando no cromo e no firefox, E NÃO no IE, Edge ou safari
ali
Eu estava procurando meu IP WAN e este site whatismyip.com também me deu meu IP local e acho que tem algo a ver com JS.
Shayan
@ali Você está correto, o site que mencionei acima não é capaz de informar meu IP local no Edge.
Shayan
6
O Google Chrome está ocultando o IP local por padrão. Ele mostra algo semelhante a e87e041d-15e1-4662-adad-7a6601fca9fb.local . Este comportamento pode ser alterado configurando a variável # enable-webrtc-hide-local-ips-with-mdns como desabilitada no Chrome: // flags
injaon
81

Atualizar

Esta solução não funcionaria mais porque os navegadores estão corrigindo o vazamento do webrtc: para mais informações sobre isso, leia esta outra pergunta: RTCIceCandidate não retorna mais o IP


Além da resposta de uma revista, este código funciona em navegadores que suportam WebRTC (Chrome e Firefox). Ouvi dizer que há um movimento em andamento para implementar um recurso que faz os sites solicitarem o IP (como no caso de geo-localização ou mídia do usuário), embora ainda não tenha sido implementado em nenhum desses navegadores.

Aqui está uma versão modificada do código-fonte , reduziu as linhas, não fazendo nenhuma solicitação de atordoamento, pois você deseja apenas o IP local, não o IP público:

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 criando uma conexão de par fictícia para o par remoto entrar em contato conosco. Geralmente trocamos candidatos de gelo uns com os outros e, lendo os candidatos de gelo, podemos dizer o ip do usuário.

Você pode encontrar uma demonstração em -> Demonstração

mido
fonte
Obrigado por este Mido! Muito apreciado.
Sujay Phadke de
1
@dampee - Eu acredito que o Edge não oferece suporte a canais de dados no momento.
MichaelB76 de
O que é um canal de dados falso? Não consigo encontrar nenhuma referência no google
AmazingTurtle
2
observe que a API createOffer mudou para ser baseada em Promise em vez de successCallback e failCallback como parâmetros, portanto, isso pode não funcionar em versões mais recentes, consulte: developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/…
Dickeylth
10

A API WebRTC pode ser usada para recuperar o IP local do cliente.

No entanto, o navegador pode não suportá-lo ou o cliente pode tê-lo desativado por motivos de segurança. Em qualquer caso, não se deve confiar neste "hack" a longo prazo, pois é provável que seja corrigido no futuro (veja a resposta de Cullen Fluffy Jennings).

O código ECMAScript 6 abaixo demonstra como fazer isso.

/* 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 escrevo return resolve(..)ou return reject(..)como um atalho. Ambas as funções não retornam nada.

Então você pode ter algo assim:

<!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
fonte
9

Limpei a postagem do mido e depois limpei a função que eles encontraram. Isso irá retornar falseou um array. Ao testar, lembre-se de que você precisa recolher o array no console do desenvolvedor da web, caso contrário, seu comportamento padrão não intuitivo pode enganá-lo ao pensar que ele está retornando um vazio 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;
}

Além disso, lembre-se, pessoal, de que isso não é algo novo como o CSS, border-radiusembora seja um daqueles bits que não é totalmente suportado pelo IE11 e anteriores. Sempre use a detecção de objetos, teste em navegadores razoavelmente mais antigos (por exemplo, Firefox 4, IE9, Opera 12.1) e certifique-se de que seus scripts mais recentes não estão quebrando seus bits de código mais recentes. Além disso, sempre detecte o código compatível com os padrões primeiro, portanto, se houver algo com, digamos, um prefixo CSS, detecte o código padrão não prefixado primeiro e, em seguida, retroceda, pois a longo prazo o suporte será padronizado para o resto de sua existência.

John
fonte
você está declarando novamente ip- linha 3 e linha 8.
user2757813
@Anu WebRTC não foi introduzido até o Internet Explorer 15 (ou "Edge 15"), então não. É por isso que na quarta linha acima, se nenhum dos objetos existir, a função retornará falso. Se houver outra maneira de fazer isso no IE, não estou ciente dela no momento.
John
@John - como passamos o valor de retorno para uma variável php? Por meio de uma postagem oculta?
MarcoZen
@MarcoZen Você pode usar <input name="example1" type="hidden" value="whatever" />ou usar um AJAX POST em tal situação. Recomendo enfaticamente estudar minha ajax()função aqui: jabcreations.com/docs/javascript
John
Acabei de descobrir que alguns navegadores (por exemplo, Chrome) agora bloqueiam o fornecimento do IP - o mesmo código agora é resolvido para um nome de host mDNS, se nenhuma permissão de Vídeo / Áudio for solicitada. Consulte 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
fonte
Use as opções do editor para formatar seu código de maneira adequada.
31 de
3
Seria ótimo se você não apenas descartasse alguns códigos, mas também desse uma explicação sobre o que está acontecendo no código dele e no seu. Ajuda o autor da pergunta e outros usuários. É bom se funcionar, mas saber o porquê é ainda mais importante na minha opinião.
davejal
alguma solução compatível com o IE?
Anu
1
O comentário é uma cópia e cola deste artigo: ourcodeworld.com/articles/read/257/…
Darkshifty
Acabei de descobrir que alguns navegadores (por exemplo, Chrome) agora bloqueiam o fornecimento do IP - o mesmo código agora é resolvido para um nome de host mDNS, se nenhuma permissão de Vídeo / Áudio for solicitada. Consulte groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU
Christoph Bimminger
5

Chrome 76+

No ano passado, usei a resposta do Linblow (19 de outubro de 2018) para descobrir com sucesso meu IP local via javascript. No entanto, as atualizações recentes do Chrome (76?) Conquistaram esse método para que agora ele retorne um IP ofuscado, como:1f4712db-ea17-4bcf-a596-105139dfd8bf.local

Se você tiver controle total sobre seu navegador, poderá desfazer esse comportamento desativando-o nos sinalizadores do Chrome, digitando o seguinte na barra de endereço:

chrome://flags

e DESATIVANDO a bandeira Anonymize local IPs exposed by WebRTC

No meu caso, exijo o IP de um script TamperMonkey para determinar minha localização atual e fazer coisas diferentes com base na minha localização. Também tenho controle total sobre as configurações do meu próprio navegador (sem políticas corporativas, etc.). Então, para mim, mudar a chrome://flagsconfiguração resolve.

Fontes:

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

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

cssifo
fonte
essa bandeira pode ir embora. Atualmente, parece que as extensões ainda obtêm IPs, portanto, você pode tentar obtê-las no script de segundo plano. A longo prazo, todas as apostas estão canceladas.
Philipp Hancke de
1
De acordo com groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU , o IP ainda deve ser retornado se você implementar sua solução que solicita permissão de Áudio / Vídeo.
Christoph Bimminger
4

Você pode encontrar mais informações sobre quais limitações os navegadores provavelmente irão adicionar para mitigar isso e o que a IETF está fazendo sobre isso, bem como por que isso é necessário no IETF SPEC no tratamento de IP

Cullen Fluffy Jennings
fonte
0

Um RTCPeerConnectionpode ser usado. Em navegadores como o Chrome, onde uma getUserMediapermissão é necessária , podemos apenas detectar os dispositivos de entrada disponíveis e solicitá-los.

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
fonte