130

I know the initial reaction to this question is "no" and "it can't be done" and "you shouldn't need it, you are doing something wrong". What I'm trying to do is get the users LAN IP address, and display it on the web page. Why? Because that's what the page I'm working on is all about, showing as much information as possible about you, the visitor: https://www.whatsmyip.org/more-info-about-you/

So I'm not actually DOING anything with the IP, other than showing it to the user for informational purposes. I used to do this by using a small Java applet. It worked pretty well. But these days, browser make you hit agree and trust so many times, to run even the most minor java applet, that I'd rather not run one at all.

So for a while I just got rid of this feature, but I'd like it back if possible. It was something that I, as a computer consultant, would actually use from time to time. It's faster to go to this website to see what IP range a network is running on, than it is to go into System Preferences, Networking, and then whatever interface is active.

So I'm wondering, hoping, if there's some way to do it in javascript alone? Maybe some new object you can access, similar to the way javascript can ask the browser where is geographic location on earth is. Maybe theres something similar for client networking information? If not, perhaps theres some other way entirely to do it? The only ways I can think of are a java applet, or a flash object. I'd rather not do either of those.

l008com
  • 1,699
  • 2
  • 11
  • 20
  • 1
    You know the answer. Why asking then? Java applets or flash objects are unlikely to be allowed by users (may be only by those who're new in the Internet) - so it's not a solution in common case. ActiveX and nearby stuff is working only in IE - and, thus, users of other browsers will not be affected (and, more, even in IE there is a security policy which prevents web-site from doing nasty things) – Alma Do Nov 25 '13 at 13:55
  • My IP address is captured thru `HTTP_X_FORWARDED_FOR` on that page, just sayin`. – tomdemuyt Nov 25 '13 at 14:12
  • 79
    Why ask then? Because maybe, just maybe, I don't know everything. – l008com Nov 26 '13 at 00:18
  • Just a headsup, the latest releases of Chrome as of this writing are now failing. 'ip' is not returned by candidate, instead 'address' is returned which appears to be MAC, not IP. There were warnings in the RTC docs about deprecating the callback interface. So, the approved answer (using Promise) might still be ok. Will test later - still works on MOZ. – Dominic Cerisano Jun 22 '19 at 14:37
  • 1
    These guys do it: http://www.whatismyproxy.com/ – likebike Sep 29 '19 at 11:32
  • 1
    @likebike Nice one. Looking into how they are doing this. – Dominic Cerisano Nov 16 '19 at 00:42

9 Answers9

135

As it turns out, the recent WebRTC extension of HTML5 allows javascript to query the local client IP address. A proof of concept is available here: http://net.ipcalf.com

This feature is apparently by design, and is not a bug. However, given its controversial nature, I would be cautious about relying on this behaviour. Nevertheless, I think it perfectly and appropriately addresses your intended purpose (revealing to the user what their browser is leaking).

afourney
  • 1,657
  • 1
  • 12
  • 10
  • 1
    This was helpful. Thanks again! – Ansuraj Khadanga Jun 02 '16 at 13:17
  • 7
    It is just working on the chrome and firefox, And NOT on the IE, Edge or safari – ali Dec 16 '16 at 00:24
  • 1
    I was looking up my WAN IP and this website https://www.whatismyip.com/ also gave me my local IP and I guess it had something to do with JS. – Shayan Jul 13 '19 at 14:21
  • @ali You're correct, the website I mentioned above is not able to tell my local IP on Edge. – Shayan Jul 13 '19 at 14:22
  • 16
    Google Chrome is hiding local IP by default. It shows something similar to *e87e041d-15e1-4662-adad-7a6601fca9fb.local* . This behaviour can be changes by setting the variable **#enable-webrtc-hide-local-ips-with-mdns** to **disabled** in Chrome://flags – injaon Sep 11 '19 at 14:51
  • looking at cleancss.com and having that flag on "Default", it still works. On other test sites, hiding works. Strange. – Yosh Nov 27 '19 at 14:39
  • It seems that in Chrome Extensions context the execution is trusted by default, and so you get the local IP even if #enable-webrtc-hide-local-ips-with-mdns is enabled – Enrico Murru Mar 18 '20 at 16:24
  • Just checked on Firefox 83. The local address has also become `{some random UUID}.local` like Chrome. – Koala Yeung Dec 03 '20 at 08:56
  • 2
    This does not works any more, the object `RTCSessionDescription` return `0.0.0.0` as local ipV4 address. It was returning the local ip till about Firefox 80 – NVRM May 15 '21 at 07:01
96

Update

This solution would not longer work because browsers are fixing webrtc leak: for more info on that read this other question: RTCIceCandidate no longer returning IP


In addition to afourney's answer this code works in browsers that support WebRTC (Chrome and Firefox). I heard there is a movement going on to implement a feature that makes sites request the IP (like in case of user's geo-location or user-media) though it has yet to be implemented in either of those browsers.

Here is a modified version of the source code, reduced the lines, not making any stun requests since you only want Local IP not the Public IP:

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;
 }
};

We are creating a dummy peer connection for the remote peer to contact us. We generally exchange ice candidates with each other and reading the ice candidates we can tell the ip of the user.

You can find a demo at --> Demo

mido
  • 24,198
  • 15
  • 92
  • 117
  • Thanks for this Mido! Much appreciated. – Sujay Phadke Apr 16 '16 at 03:14
  • 1
    @dampee - I believe Edge does not support data channels at the moment. – MichaelB76 Apr 20 '16 at 10:28
  • What is a bogus data channel? Can't find any reference on google – AmazingTurtle Jun 05 '19 at 14:39
  • 2
    note the createOffer api has switched to be based on Promise instead of successCallback and failCallback as params, so this may not work on newer versions, see: https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer#Deprecated_parameters – Dickeylth Sep 26 '19 at 12:11
  • Doesn't seem to function for me. None of the address kinds are returned. `Error with Permissions-Policy header: Unrecognized feature: 'interest-cohort'. (index):63 Uncaught TypeError: Cannot read properties of null (reading '1') at handleCandidate ((index):63:59) at RTCPeerConnection.pc.onicecandidate ((index):77:25) (index):63 Uncaught TypeError: Cannot read properties of null (reading '1') at handleCandidate ((index):63:59) at (index):98:29 at Array.forEach () at (index):96:27` – zmechanic Feb 05 '22 at 20:02
13

The WebRTC API can be used to retrieve the client's local IP.

However the browser may not support it, or the client may have disabled it for security reasons. In any case, one should not rely on this "hack" on the long term as it is likely to be patched in the future (see Cullen Fluffy Jennings's answer).

The ECMAScript 6 code below demonstrates how to do that.

/* 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();
    };
} );

Notice I write return resolve(..) or return reject(..) as a shortcut. Both of those functions do not return anything.

Then you may have something this :

<!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
  • 449
  • 5
  • 7
10

I cleaned up mido's post and then cleaned up the function that they found. This will either return false or an array. When testing remember that you need to collapse the array in the web developer console otherwise it's nonintuitive default behavior may deceive you in to thinking that it is returning an empty 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;
}

Additionally please keep in mind folks that this isn't something old-new like CSS border-radius though one of those bits that is outright not supported by IE11 and older. Always use object detection, test in reasonably older browsers (e.g. Firefox 4, IE9, Opera 12.1) and make sure your newer scripts aren't breaking your newer bits of code. Additionally always detect standards compliant code first so if there is something with say a CSS prefix detect the standard non-prefixed code first and then fall back as in the long term support will eventually be standardized for the rest of it's existence.

John
  • 1
  • 13
  • 98
  • 177
  • you're redeclaring `ip` - line 3 and line 8. – user2757813 Apr 13 '18 at 14:23
  • @Anu WebRTC was not introduced until Internet Explorer 15 (or "Edge 15") so no. That is why on the fourth line above if none of the objects exists the function will return false. If there is another way of achieving this in IE then I'm not aware of it at this time. – John Jun 03 '18 at 13:06
  • @John - how do we pass the return value to a php variable ? Via a hidden post ? – MarcoZen Jun 09 '18 at 08:10
  • @MarcoZen You can either use `` or use an AJAX POST in such a situation. I highly recommend studying my `ajax()` function here: https://www.jabcreations.com/docs/javascript/ – John Jun 09 '18 at 12:17
  • Just found out that some browsers (e.g. Chrome) now block providing the IP - same code now resolves to an mDNS hostname, if no Video/Audio permission is requested. See https://groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU – Christoph Bimminger Dec 22 '19 at 04:52
8

Chrome 76+

Last year I used Linblow's answer (2018-Oct-19) to successfully discover my local IP via javascript. However, recent Chrome updates (76?) have wonked this method so that it now returns an obfuscated IP, such as: 1f4712db-ea17-4bcf-a596-105139dfd8bf.local

If you have full control over your browser, you can undo this behavior by turning it off in Chrome Flags, by typing this into your address bar:

chrome://flags

and DISABLING the flag Anonymize local IPs exposed by WebRTC

In my case, I require the IP for a TamperMonkey script to determine my present location and do different things based on my location. I also have full control over my own browser settings (no Corporate Policies, etc). So for me, changing the chrome://flags setting does the trick.

Sources:

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

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

Community
  • 1
  • 1
cssyphus
  • 37,875
  • 18
  • 96
  • 111
  • that flag might go away. It currently seems like extensions still get IPs so you might try to get it from the background script. Long-term all bets are off though. – Philipp Hancke Nov 20 '19 at 22:05
  • 2
    According to https://groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU, the IP should still be returned if you implement your solution that it requests Audio/Video permission. – Christoph Bimminger Dec 22 '19 at 04:53
  • I was looking for this for the exact same reason, and lmao when I read the last paragraph of your post. Can't believe this is still possible in late 2020, but so glad that it is! +1 for sharing. – Kenny83 Sep 13 '20 at 19:51
7

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)
ADJenks
  • 2,973
  • 27
  • 38
  • Please use the editor options to format your code appropriately. – 31piy Mar 14 '17 at 17:33
  • 3
    It would be great if you'd not only just drop some code, but also give an explanation of what is going on in his and your code. It helps the question author and other users. It's good if it works, but knowing why is even more important in my opinion. – davejal Mar 14 '17 at 18:04
  • any IE compatible solutions? – Anu Jun 01 '18 at 07:40
  • 1
    The comment is a copy paste of this article: https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only – Darkshifty Aug 29 '19 at 12:56
  • Just found out that some browsers (e.g. Chrome) now block providing the IP - same code now resolves to an mDNS hostname, if no Video/Audio permission is requested. See https://groups.google.com/forum/#!topic/discuss-webrtc/6stQXi72BEU – Christoph Bimminger Dec 22 '19 at 04:52
5

Now supported in internal-ip!

An RTCPeerConnection can be used. In browsers like Chrome where a getUserMedia permission is required, we can just detect available input devices and request for them.

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
  • 7,738
  • 4
  • 38
  • 58
  • Thank you! That's exactly what I needed. Just Javascript, no HTML and understandable code :) – JackieNBee Oct 06 '20 at 14:47
  • 1
    Hahaha so I had to allow access to my microphone for this but it ended up spitting out the IP address for hyperv vswitch – Dmitri DB Feb 18 '21 at 21:42
4

You can find more info about what limitations browsers will likely add to mitigate this and what IETF is doing about it as well as why this is needed at IETF SPEC on IP handling

Community
  • 1
  • 1
-4

Try to use the OS package to find the wifi ip.

const os = require('os');
console.log('IP Address: ' + JSON.stringify(os.networkInterfaces()['Wi-Fi']).match(/"192.168.\d+.\d+"/g)[0])
Codemaker2015
  • 12,190
  • 6
  • 97
  • 81