0

Below is a quote from this GitHub project STUN IP Address requests for WebRTC.

These request results are available to JavaScript, so you can now obtain a users local and public IP addresses in JavaScript.

I did as suggested in the following quote.

Here is the annotated demo function that makes the STUN request. You can copy and paste this into the Firefox or Chrome developer console to run the test.

The result was a script error with output results of undefined and 192.168.x.x. It did correctly detect the internal home IP address of one of my laptops.

The error was:

Uncaught TypeError: Cannot read property '1' of null at handleCandidate (:38:47) at RTCPeerConnection.pc.onicecandidate (:52:13)

The error occurred here:

var ip_addr = ip_regex.exec(candidate)[1];

More data

For the internal network IP case that worked, the candidate value was: candidate:1178812653 1 udp 2113937151 192.168.x.x 52663 typ host generation 0 ufrag syTM network-cost 50.

Corrected update

I had the console.log after the handleCandidate, which was why I did not see the second result. I have updated the code with console.log entries.

The second ice event fails because an IPv6 address is returned instead of the client's public IP address:

2299073356 1 udp 2113932031 2001::9d38:953c:1c28:17c0:xxx:xxx 52281 typ host generation 0 ufrag NQtJ network-cost 50


Question:

Is this approach still viable for detecting the client's public IP address? If it is, do you know what is broken with the GitHub code?

Included Code here:

         //get the IP addresses associated with an account
    function getIPs(callback){
    var ip_dups = {};

    //compatibility for firefox and chrome
    var RTCPeerConnection = window.RTCPeerConnection
        || window.mozRTCPeerConnection
        || window.webkitRTCPeerConnection;
    var useWebKit = !!window.webkitRTCPeerConnection;

    //bypass naive webrtc blocking using an iframe
    if(!RTCPeerConnection){
        //NOTE: you need to have an iframe in the page right above the script tag
        //
        //<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe>
        //<script>...getIPs called in here...
        //
        var win = iframe.contentWindow;
        RTCPeerConnection = win.RTCPeerConnection
            || win.mozRTCPeerConnection
            || win.webkitRTCPeerConnection;
        useWebKit = !!win.webkitRTCPeerConnection;
    }

    //minimal requirements for data connection
    var mediaConstraints = {
        optional: [{RtpDataChannels: true}]
    };

    var servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};

    //construct a new RTCPeerConnection
    var pc = new RTCPeerConnection(servers, mediaConstraints);

    function handleCandidate(candidate){
        //match just the IP address
        var ip_regex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
        console.log("candidate in handler" + candidate);
        var ip_addr = ip_regex.exec(candidate)[1];

        //remove duplicates
        if(ip_dups[ip_addr] === undefined)
            callback(ip_addr);

        ip_dups[ip_addr] = true;
    }
    var count = 1;
    //listen for candidate events
    pc.onicecandidate = function(ice){
        console.log("ice event " + count + ": " + ice)
        //skip non-candidate events
        var propertyCount = 1
        if(ice.candidate){
            console.log("ice candidate " + count + ": " + ice.candidate.candidate); 
            handleCandidate(ice.candidate.candidate);
        }
        count++;
    };

    //create a bogus data channel
    pc.createDataChannel("");

    //create an offer sdp
    pc.createOffer(function(result){

        //trigger the stun server request
        pc.setLocalDescription(result, function(){}, function(){});

    }, function(){});

    //wait for a while to let everything done
    setTimeout(function(){
        //read candidate info from local description
        var lines = pc.localDescription.sdp.split('\n');

        lines.forEach(function(line){
            if(line.indexOf('a=candidate:') === 0)
                handleCandidate(line);
        });
    }, 1000);
    }

    //Test: Print the IP addresses into the console
    getIPs(function(ip){console.log(ip);});
Highdown
  • 646
  • 1
  • 11
  • 23

1 Answers1

1

Yes, making STUN requests with WebRTC to determine the client's IP address is still a viable approach. To determine what's wrong with the particular code you have posted, try dumping candidate in handleCandidate() to see why the ip_regex regexp is choking:

function handleCandidate(candidate){
    console.log(candidate);
    ...
}

Edit: It looks like the problem was with the STUN server used. I've replaced stun.services.mozilla.com with stun.l.google.com:19302 and I'm getting my public IP address in the console, alongside the private one.

jamix
  • 5,484
  • 5
  • 26
  • 35
  • I added test data for the candidate in my question. It works for the internal network client IP address, but NOT for the public IP address. Two events fire, but only the local IP address is correctly returned. I have been studying the info on Mozilla site, but have not made any progress beyond this point. Thanks... – Highdown Jan 03 '18 at 14:32
  • That's strange. Can you also log `ice` in your `onicecandidate` handler? – jamix Jan 03 '18 at 14:40
  • I have included the console.log insertions in the code. I had placed a console.log after the call to updateCandidate, which explains the incorrect results that I had previously entered. I have updated the question. It appears that the second value returned now is the local IPv6 address, which was causing the regex failure. That seems to explain the failure, but apparently this technique no longer work for the client's public IP address. – Highdown Jan 03 '18 at 15:38
  • From the looks of it, the regexp is actually supposed to match IPv6 addresses as well. Apparently there's a mistake that makes it choke. Answer updated with the actual fix for your issue (using a working STUN server). – jamix Jan 03 '18 at 15:53
  • 1
    I verified the fix, and checked your answer as the correct one. Much thanks!!! My next trial is to get this working in Typescript in Visual Studio. – Highdown Jan 03 '18 at 16:12