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);});
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.
Related
I'm new to Blockchain and I want to understand how to use JS to open a Websocket, the code is from Blockcypher.com docs https://www.blockcypher.com/dev/dash/?javascript#using-websockets
// Get latest unconfirmed transactions live
var ws = new WebSocket("wss://socket.blockcypher.com/v1/dash/main");
var count = 0;
ws.onmessage = function (event) {
var tx = JSON.parse(event.data);
var shortHash = tx.hash.substring(0, 6) + "...";
var total = tx.total / 100000000;
var addrs = tx.addresses.join(", ");
$('#browser-websocket').before("<div>Unconfirmed transaction " + shortHash + " totalling " + total + "DASH involving addresses " + addrs + "</div>");
count++;
if (count > 10) ws.close();
}
ws.onopen = function(event) {
ws.send(JSON.stringify({event: "unconfirmed-tx"}));
}
The browser console displays this error: Firefox can’t establish a connection to the server at wss://socket.blockcypher.com/v1/dash/main.
Note: I have a token provided by Blockcypher but the code example they provide doesn't ask for any token. Is the code bad? is it missing something?
The problem is not in your code but with the server not responding as expected.
It could be a server issue or you may need to be pre approved to access the resource (assuming they are whitelisting IP addresses which I doubt).
Also, check out the rest of the code, it has a placeholder for a token that you may need to connect (the error you see now is way before the token is requested).
You can always change the server name to one of many test servers out there like wss://echo.websocket.org and see the error changing from "Can't establish connection" to something related to the rest of the code.
I used the following code snippet to get results on both the web side and the sandbox side.However, in my React code debugging, I always failed...
//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})/
var ip_addr = ip_regex.exec(candidate)[1];
//remove duplicates
if(ip_dups[ip_addr] === undefined)
callback(ip_addr);
ip_dups[ip_addr] = true;
}
//listen for candidate events
pc.onicecandidate = function(ice){
//skip non-candidate events
if(ice.candidate)
handleCandidate(ice.candidate.candidate);
};
//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);});
ICE failed, add a STUN server and see about:webrtc for more
details:
canTrickleIceCandidates: null
currentLocalDescription: null
currentRemoteDescription: null
iceConnectionState: "failed"
iceGatheringState: "gathering"
idpLoginUrl: null
localDescription: RTCSessionDescription`
onaddstream: null
onaddtrack: null
ondatachannel: null
onicecandidate:Login/</pc.onicecandidate()
oniceconnectionstatechange: null
onicegatheringstatechange: null
onnegotiationneeded: null
onsignalingstatechange: null
ontrack: null
peerIdentity: Promise { "pending" }
since Safari updated to version 11 we can use WebRTC API. However, I'm trying to get the client IP address (local IP, i.e. 192.168.1.10) but there is no result.
The code I'm using is the one you can find in several guides. The same code works on Chrome and Firefox, which are compatibles with this API before than Safari. It's something like this:
/**
* Get the user IP throught the webkitRTCPeerConnection
* #param onNewIP {Function} listener function to expose the IP locally
* #return undefined
*/
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;
}
//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);
};
}
// Usage
getUserIP(function(ip){
alert("Got IP! :" + ip);
});
I've been debugging and I figured out that ice.candidate is not defined, so there isn't any IP to iterate in code.
Any idea or alternative?
Thank you.
Safari is implementing the latest version of the spec which, for security reasons, prevent local candidates for being generated. You have an option in the browser that allow you to give safari the permission to do it, but it needs to be done manually. Other browsers are not fully compliant yet and still allow local candidates to be generated.
In the developper menu you can chose to stop filtering the candidates.
https://i1.wp.com/webrtcbydralex.com/wp-content/uploads/2017/06/Screen-Shot-2017-06-16-at-3.20.30-PM.png
I have two clients :
1) Chrome (version 50.0.2661.102 m) on Windows 7 PC
2) Chrome (version 50.0.2661.89) on Android tablet
Both are in the same network (so no need for STUN/TURN server).
I use my own signal server built with node.js (webSocket) on a VirtualBox VM with Centos 6.
The communication with video/sound between the clients works fine. Now I want to transfer a file from one client to another. As base of my code i use the code of this example
here
As this code suggess, I create the dataChannnel exactly after the creation of PeerConnection.
function createPeerConnection() {
....
myPeerConnection = new RTCPeerConnection(iceServers, optional);
myDataChannel = myPeerConnection.createDataChannel('myDataChannel');
// Set up event handlers for the ICE negotiation process.
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.onaddstream = handleAddStreamEvent;
myPeerConnection.onnremovestream = handleRemoveStreamEvent;
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
myPeerConnection.ondatachannel = handleDataChannel;
myDataChannel.onmessage = handleDataChannelMessage;
myDataChannel.onopen = handleDataChannelOpen;
}
...
...
function invite(peerId) {
...
createPeerConnection();
...
}
...
...
function handleVideoOfferMsg(msg) {
thereIsNegotiation = true;
targetUsername = msg.name;
// Call createPeerConnection() to create the RTCPeerConnection.
log("Starting to accept invitation from " + targetUsername);
createPeerConnection();
// We need to set the remote description to the received SDP offer
// so that our local WebRTC layer knows how to talk to the caller.
var desc = new RTCSessionDescription(msg.sdp);
myPeerConnection.setRemoteDescription(desc)
.then(function(stream) {
log("-- Calling myPeerConnection.addStream()");
return myPeerConnection.addStream(localStream);
})
.then(function() {
log("------> Creating answer");
// Now that we've successfully set the remote description, we need to
// start our stream up locally then create an SDP answer. This SDP
// data describes the local end of our call, including the codec
// information, options agreed upon, and so forth.
return myPeerConnection.createAnswer();
})
.then(function(answer) {
log("------> Setting local description after creating answer");
// We now have our answer, so establish that as the local description.
// This actually configures our end of the call to match the settings
// specified in the SDP.
return myPeerConnection.setLocalDescription(answer);
})
.then(function() {
var msg = {
name: clientId,
room: roomId,
target: targetUsername,
type: "video-answer",
sdp: myPeerConnection.localDescription
};
// We've configured our end of the call now. Time to send our
// answer back to the caller so they know that we want to talk
// and how to talk to us.
log("Sending answer packet back to other peer");
sendToServer(msg);
})
.catch(handleGetUserMediaError);
}
When the second client makes the offer, the first client when tries to make the answer, I get the error
Error opening your camera and / or microphone : failed to set local answer
spd: Failed to push down transport description: Local fingerprint provided
but no identity available.
or
Error opening your camera and / or microphone : failed to set local answer
spd: Called in wrong state : STATE_INPROGRESS
Only one time the creation was successful.
Do I have to create DataChannel in other place? Like here :
function handleICEConnectionStateChangeEvent {
switch(myPeerConnection.iceConnectionState) {
...
case "connected":
createDataChannel();
break;
}
}
function createDataChannel(){
myDataChannel = myPeerConnection.createDataChannel('myDataChannel');
myPeerConnection.ondatachannel = handleDataChannel;
myDataChannel.onmessage = handleDataChannelMessage;
myDataChannel.onopen = handleDataChannelOpen;
}
Any suggestions?
The error in this code is that both sender and receiver create new datachannel. The right thing is, one to create the datachannel
myDataChannel = myPeerConnection.createDataChannel('myDataChannel')
and the other to wait for the creation of dataChannel:
myPeerConnection.ondatachannel = handleDataChannel;
This question already has answers here:
How to get client's IP address using JavaScript? [closed]
(31 answers)
Closed 6 years ago.
Is posible Get Client IP Using WebRTC JS API and the ICE protocol (STUN for public IP)?
Yes you can, using WebRTC JS API and the ICE protocol (STUN for public IP).
More info 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})/
var ip_addr = ip_regex.exec(candidate)[1];
//remove duplicates
if(ip_dups[ip_addr] === undefined)
callback(ip_addr);
ip_dups[ip_addr] = true;
}
//listen for candidate events
pc.onicecandidate = function(ice){
//skip non-candidate events
if(ice.candidate)
handleCandidate(ice.candidate.candidate);
};
//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);});